fix(dynamodb): handle IN (...) and SET list[N] UpdateExpression#261
Merged
vieiralucas merged 2 commits intofaiscadev:mainfrom Apr 12, 2026
Merged
Conversation
Both are silent-fallthrough bugs in the expression evaluator.
evaluate_single_filter_condition had no handling for IN. Expressions
like "#s IN (:a, :b)" fell through to evaluate_single_key_condition,
hit none of the comparison operators, and returned `true` — so every
item matched every IN filter, and because evaluate_condition now
delegates to evaluate_filter_expression, every conditional write with
an IN clause was silently accepted regardless of state.
apply_set_assignment called resolve_attr_name on the whole "#items[0]"
token, which never hits the name map, then did item.insert("#items[0]",
:v) — producing a top-level attribute literally named "#items[0]"
rather than mutating the L array.
Fix: new parse_in_expression / evaluate_in_match helpers wired into
evaluate_single_filter_condition before the key-condition fallthrough
(case-insensitive " IN ", tolerates no-space-after-comma forms that
hand-built "IN (" + strings.Join(keys, ",") + ")" expressions produce).
New parse_list_index_suffix / assign_list_index helpers split the [N]
suffix off before resolve_attr_name, then replace slot N of the L array
(or append if N == len, no-op beyond).
10 new unit tests: IN match/no-match/no-space/missing-attr/compound in
both filter and condition paths, SET list-index replace slot 0, replace
slot 1 of 3, literal attribute name. Each was verified to fail against
upstream/main before the fix and pass after.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
627ef70 to
ec2777f
Compare
aws-sdk-go v2's expression.NewBuilder emits function calls with a
space between the name and the opening paren:
(attribute_exists (#0)) AND ((attribute_not_exists (faiscadev#1)) OR (faiscadev#1 = :0))
extract_function_arg was only matching "attribute_exists(" with no
space, so any ConditionExpression built through the official SDK's
builder fell through every branch of evaluate_single_filter_condition,
hit evaluate_single_key_condition, failed to match any comparison
operator, and returned `true` at the bottom of the function. Every
claim-lease / "update if exists" write was silently accepted
regardless of item state.
The begins_with and contains branches already accept both forms
("begins_with(" and "begins_with (") — this mirrors that pattern
for the attribute_exists / attribute_not_exists path.
Verified fix end-to-end against the orderbot claim-lease tests
(TestClaimLease_NotFound, TestClaimLease_RejectsWhenHeldByOtherTab,
TestLeaseLifecycle_TwoTabs) by running them with FAKECLOUD_BIN
pointing at a locally-built binary — 114 service tests pass, 0 fail.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three silent-fallthrough bugs in the DynamoDB expression evaluator. All three hit callers using
aws-sdk-*'s expression builder.1.
IN (...)silently accepted everythingevaluate_single_filter_conditionhad noINbranch.#s IN (:a, :b)fell through toevaluate_single_key_condition, matched no operator in its<=/>=/<>/=/</>loop, and returnedtrueat the bottom of the function. Sinceevaluate_conditiondelegates toevaluate_filter_expression(postd3fe7c4), the same silent-true affected ConditionExpressions — every conditional write with anINclause was silently accepted regardless of actual state.Fix: new
parse_in_expression/evaluate_in_matchhelpers wired intoevaluate_single_filter_conditionbefore its existing fallthrough. Case-insensitive, trimmed, tolerates missing spaces after commas — hand-built expressions like"#status IN (" + strings.Join(keys, ",") + ")"produce no-space forms that are valid per the DynamoDB grammar. Missing attributes never match.2.
SET #list[N] = :vsilently created a top-level keyapply_set_assignmentcalledresolve_attr_nameon the whole"#items[0]"token.resolve_attr_nameonly matches#-prefixed names verbatim, so the[0]caused a miss — the function then diditem.insert("#items[0]", :item), producing a new top-level attribute literally named"#items[0]"rather than mutating theLarray.Fix: new
parse_list_index_suffixsplits#items[0]into("#items", 0)before the name-ref lookup. Newassign_list_indexwalks theLarray and replaces slotN(or appends ifN == len, no-op beyond). Non-list-index SET paths (if_not_exists,list_append, arithmetic, plain assignment) are untouched. Deeper nested paths (a.b[0],list[0].field) are still unsupported and left for a follow-up.3.
attribute_exists (x)with a space silently passedaws-sdk-go v2's
expression.NewBuilderemits function calls with a space between the name and the opening paren:extract_function_argwas only matchingattribute_exists(with no space, so any ConditionExpression built through the official SDK's expression builder fell through every branch ofevaluate_single_filter_condition, hitevaluate_single_key_condition, failed to match any comparison operator, and returnedtrueat the bottom of the function. Everyattribute_exists/attribute_not_existscheck that went through the SDK builder was silently accepted regardless of item state — including the classic "claim-lease" compound pattern above.Caught this by tracing the exact string fakecloud received while running
orderbot'sReceivingSessionService.ClaimLeaseintegration tests:Fix:
extract_function_argnow accepts bothfunc_name(andfunc_name (, mirroring the pattern already in place forbegins_withandcontainsin the filter leaf.Test plan
service.rs:(#s IN (:a, :b)) AND (#p = :h)(both match and mismatch), condition match, condition no-match.#ref).(attribute_exists (#0)) AND ((attribute_not_exists (#1)) OR (#1 = :0))shape against four item states (free, missing, held-by-other, self-held).cargo test -p fakecloud-dynamodb— 68 pass (was 57, +11 new)cargo test -p fakecloud-e2e --test dynamodb— 36 passcargo clippy -p fakecloud-dynamodb -- -D warnings— cleancargo fmt --check— cleanorderbotintegration suite against a locally-built binary from this branch viaFAKECLOUD_BIN=/tmp/fakecloud-debug go test ./lambdas/api/services/...— 114 service tests pass, including the three that were previously failing onv0.7.1:TestClaimLease_NotFound,TestClaimLease_RejectsWhenHeldByOtherTab,TestLeaseLifecycle_TwoTabs.