-
-
Notifications
You must be signed in to change notification settings - Fork 502
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
Atomic Update Support #3657
Comments
Re-reading this now, I am not sure I got it right. I would like to add support for a delayed atomic update that will be done via explicit flush. This way it would be always done inside transaction, it is up to you to define explicit boundary if you want to, as well as to flush when appropriate. Not sure if we need something more generic than increment/decrement support? const user = await em.findOne(User, id); // or `em.getReference(User, id)`
em.increment(user, 'counter');
await em.flush(); Maybe we could even find a way to do something like this: const user = await em.findOne(User, id); // or `em.getReference(User, id)`
user.counter = raw('counter + 1');
await em.flush(); This would either use a returning statement or a separate query to load the actual value and hydrate it back to the provided entity instance. So rather lower level API than what you described, as that feels quite app-specific. |
my motivation behind a more high-level approach (in this case, inc/dec) is that it allows that entity reference to be shared across app boundaries safely (in memory) without worrying about the state of a flush. for example, if you are processing a bunch of orders in batch, and want to generate an invoice for each one, you may increment an "invoice number" while looping through each order and then commit a single transaction (with multi-value inserts) at the end. i suppose with the low-level approach, you could derisk it by throwing if you attempt to "get" a property which has been assigned to a how would something like // generateNewInvoiceNumber()
const user = await em.findOne(User, id); // or `em.getReference(User, id)`
user.invoiceNumber = raw(alias => `${alias}.invoiceNumber + 1`);
// createInvoice()
const invoice = new Invoice();
invoice.number = user.invoiceNumber;
await em.persistAndFlush(invoice); |
That's a good point - I was suggesting the Internally, it would work similarly to how we work with FKs, the UoW automatically maps them to So while I do understand this can bring inconsistency to the model, it sounds like a very powerful addition - and you can always flush right ahead if you care about this. I am not entirely sure about the |
you proposal is effectively opt-in, so no harm in adding it :) i suppose if the serialization throw would cover cases like adding it into a object then running i don't think i'd like the runtime risk tho, because I could see folks doing the equivalent of
i'm not sure i follow the "flushing part", the example in the initial post wouldn't flush at all, just issue a raw query (inside a transaction) and update the entity in memory with new value. i could see making the call more generic though, so instead of use-case specific inc/dec, have the user pass in |
It would be effectively a tagged object in place of a scalar, we could define both console.log({ sql: 'select 1', toString: () => { throw new Error() } } + 1)
Your 3. point was "Flush UoW queue", that's what confused me.
But then it sounds exactly like #2397, or not? |
👍
i don't quite remember why i included this, perhaps I was concerned that if the raw query will depend on changes already in the UoW that had not been flushed yet, the atomic update will effectively ignore local state and cause unexpected issues.
don't think so, that issue was about making changes to entities that may or may not be in the UoW already. this particular ticket for me is important because TypeORM caused me so much heart burn from this exact problem, where one side of a request would increment something but another side would have a older/newer instance of a object, mutate and save and then overwrite the increment i had done somewhere else. so as long as there is some ORM blessed solution to prevent that problem, I am happy :) |
Your proposal takes a query and payload - imho it is the same. Which is what I can see achieved via that So if you'd like the results to be available right ahead, |
i think part of the motivation for what i proposed in 2397 is to explicitly NOT run a query. it would just mark the entities in memory as dirty, if they don't exist, put the updates in some type of queue, so that if a future object matching that id is hydrated, it would use the queued value. for context, when i use Mikro, my goal is to limit the number of queries per request as much as possible, so if I can deal purely with objects without touching a transaction/flushing. but yes, you are correct in that if |
Hmm looks like I misread the description, I thought it is supposed to fire the update query right ahead, given you defined it as async - but it should be rather a sync method, as it only checks in-memory state and queues the update either way. So that issue is basically the same as #2392 (which is the in-memory matching part) followed by |
exactly :) |
```ts const ref = em.getReference(User, 1); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` Closes #3657
```ts const ref = em.getReference(User, 1); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` Closes #3657
So #4094 should help with doing the atomic update via flush. |
```ts const ref = em.getReference(User, 1); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` Closes #3657
Is it an option to also allow the use of A use case I have in mind is the following: We have an entity, say
I tried to find a way to do this currently, and finally found this issue which was related, although I'm not sure if the mentioned methodology would be fitting for this use case. I'd be happy to make a separate issue for this use case if it isn't. |
It should already work with inserts in #4094, I will add some tests around that too. Inserts were already using returning statement so the code you see in that PR is mostly about updates as they were not having this support implemented. |
```ts const ref = em.getReference(User, 1); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` Closes #3657
```ts const ref = em.getReference(User, 1); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` Closes #3657
```ts const ref = em.getReference(User, 1); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` Closes #3657
I ended up disallowing the reuse of the raw statement, it holds an internal counter that is incremented on every assignment and throws it was already used. Propagating the value could be quite challenging as it could potentially require changing the order of queries during flush, and that is already enough of a complex problem. I guess it's better to just throw and let the developer handle it, probably with an explicit flush (or with that future version of |
```ts const ref = em.getReference(User, 1); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
Closing as |
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes mikro-orm#3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
When you want to issue an atomic update query via flush, you can use the static `raw()` helper: ```ts const ref = em.getReference(Author, 123); ref.age = raw(`age * 2`); await em.flush(); console.log(ref.age); // real value is available after flush ``` The `raw()` helper returns special raw query fragment object. It disallows serialization (via `toJSON`) as well as working with the value (via [`valueOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf)). Only single use of this value is allowed, if you try to reassign it to another entity, an error will be thrown to protect you from mistakes like this: ```ts order.number = raw(`(select max(num) + 1 from orders)`); user.lastOrderNumber = order.number; // throws, it could resolve to a different value JSON.stringify(order); // throws, raw value cannot be serialized ``` Closes #3657
Is your feature request related to a problem? Please describe.
Most databases include some level of auto incrementing (eg: SERIAL or generators) however these are usually not transaction safe so you need to maintain your own int column and update via a lock (usually done automatically if inside an open transaction).
Describe the solution you'd like
entityManager.atomicUpdate(MyEntity, {id:1}, {field: 'field + 1'})
entityManager
is inside a transaction, if not issue a lock command on row (if driver supports), or alternatively always ensist it is in a transactionDescribe alternatives you've considered
Only way to do this today is use
createQueryBuilder
to build an update query, execute it against the driver and then update the instance in memory your self (potential downside being that it may execute another query thinking there was a change)The text was updated successfully, but these errors were encountered: