chris-armstrong / dynaglue

Make DynamoDB single-table designs easier to query and update
Apache License 2.0
77 stars 6 forks source link

Expression values #21

Open kbanman opened 1 year ago

kbanman commented 1 year ago

I think I've run into a limitation with the library for this scenario:

Increment a numeric value by a given amount (UpdateExpression = 'SET #usage = #usage + :increase')

It seems to me that updateById only supports literal values. Do you have any plans to support this scenario?

Could follow the lead of libraries like knex, which allows you to wrap values in a "raw" container. The tricky thing here is that the final value would need to be dependent on the compiled attribute names and attribute values.

Maybe simply making an escape hatch more readily available code be enough. In any case please let me know your thoughts; I'd be happy to contribute something

chris-armstrong commented 1 year ago

I’d be happy to take a contribution. Propose an interface change and I’ll give you guidance

chris-armstrong commented 1 year ago

I always meant for this interface to take operations and not just be key:value pairs

kbanman commented 1 year ago

Here's a first attempt:

type SetValuesDocument = {
  [path: string]: Operand;
};

type SetChange = [string | KeyPath, Operand];

type Operand = KeyPath | LiteralValue | FunctionOperand;

interface FunctionOperand {
  $fn: [string, KeyPath | LiteralValue, KeyPath | LiteralValue];
}

type LiteralValue = any;

An attribute upsert would then look like

{
  $set: {
    price: { $fn: ['if_not_exists', ['price'], 123] },
  }
}

But this doesn't hold up as well for a list append:

{
  $set: {
    // #tags = list_append(#tags, :vals)
    tags: { $fn: ['list_append', ['tags'], ['newtag']] },
  }
}

The ['newtag'] member is ambiguous: it could be interpreted as a list of strings or as a KeyPath to the newtag attribute. The logic would need to use the fact that list_append needs an array as the second argument and require paths to be declared using the array notation nested inside that array. list_append also allows swapping the arguments to append to the beginning of a list.

Binary operations (+ and -) would also require paths to use the array notation to disambiguate from string values:

{
  $set: {
    // #quantity = #quantity + 1
    quantity: { $fn: ['+', ['quantity'], 1] },

    // #quantity = #quantity + #min_order_qty
    quantity: { $fn: ['+', ['quantity'], ['min_order_qty']] },
  }
}

Helper functions could be exposed to improve the dev experience.

kbanman commented 1 year ago

There is still potential for ambiguity between KeyPath and a literal nested list of strings. I haven't come up with a solution for that

chris-armstrong commented 1 year ago

I see what you mean by the ambiguity - would it help if we "broke" the API (removing backwards compatibility) and redesigned it from scratch?

chris-armstrong commented 1 year ago

Take a look at this: https://github.com/chris-armstrong/dynaglue/blob/5a3d3813c5b84d22e2a73148d142070c5a147154/src/operations/update_by_id.ts#L84