Skip to content

Code swap does not take effect when second user pulls changes #9289

Draft
khanaffan wants to merge 4 commits into
masterfrom
affank/code-swap
Draft

Code swap does not take effect when second user pulls changes #9289
khanaffan wants to merge 4 commits into
masterfrom
affank/code-swap

Conversation

@khanaffan
Copy link
Copy Markdown
Contributor

@khanaffan khanaffan commented May 12, 2026

imodel-native: iTwin/imodel-native#1439

Summary

When a second user pulls changes, code swaps do not take effect. This is caused by the unique index constraint combined with our conflict handler that is configured to skip on conflict. As a result, any swaps are silently lost.

What Is a Code Swap?

A code swap exchanges the CodeValue between two elements. Because CodeValue is protected by a unique index, a direct swap is not possible in a single step. Instead, the swap is implemented as:

  1. Read both current CodeValues into temporary variables (tempA, tempB).
  2. Set the first element's CodeValue to null (to release the unique index slot).
  3. Set the first element's CodeValue to tempB (second element's original value).
  4. Set the second element's CodeValue to tempA (first element's original value).

Cross-transaction variant

When the swap spans two separate transactions, it works correctly:

  • Txn 1: Set first element's CodeValuenull
  • Txn 2: Assign final values to both elements

Each transaction satisfies the unique index in isolation, so no conflict occurs.

Same-transaction variant (broken for undo/redo)

When all four steps occur within a single transaction, undo/redo skips the intermediate null assignment because the conflict handler sees the unique index violation during replay and issues a SKIP. The swap is silently dropped.

This same failure mode also manifests when:

  • Transactions are squashed before being pushed to iModelHub.
  • A second user pulls and applies the squashed changeset — the conflict handler skips the intermediate step, and the swap never lands.

Root Cause

During replay of the swap operations, the conflict handler encounters a unique index violation on the intermediate or final CodeValue assignment and chooses to SKIP rather than fail or retry. This causes the swap to be silently lost for any user replaying the changeset.

Sequence Diagram

sequenceDiagram
    participant U1 as User 1 (Author)
    participant Hub as iModelHub
    participant U2 as User 2 (Puller)

    Note over U1: Swap CodeValue: Element A ↔ Element B
    U1->>U1: tempA = A.CodeValue, tempB = B.CodeValue
    U1->>U1: Txn 1 — Set A.CodeValue = null
    U1->>U1: Txn 2 — Set A.CodeValue = tempB
    U1->>U1: Txn 2 — Set B.CodeValue = tempA
    Note over U1: ✅ Swap succeeds locally<br/>(unique index satisfied per txn)

    U1->>Hub: Push changesets (possibly squashed)
    Note over Hub: Squashed changeset may collapse<br/>Txn 1 + Txn 2 into one delta

    U2->>Hub: Pull changesets
    Hub->>U2: Apply squashed changeset
    Note over U2: Replay: Set A.CodeValue = tempB
    Note over U2: ❌ Unique index violation detected<br/>(A.CodeValue still = tempA)
    U2-->>U2: Conflict handler: SKIP
    Note over U2: ❌ Swap is lost —<br/>U2 retains original stale values

    Note over U1,U2: Same failure occurs within a single txn<br/>during undo/redo replay
Loading

Impact

  • Code swaps authored by one user are not reflected in the working copy of a second user after pulling.
  • Undo/redo of a same-transaction swap is also silently broken.
  • No error is surfaced to the user in either case — this is silent data loss.

Status

This issue has been reported to the SQLite developers and they are actively working on a fix at the SQLite level. In the meantime, a workaround or mitigation may be needed on our side to detect and re-apply lost swaps.

Related

  • Affects collaborative / multi-user workflows
  • Affects undo/redo within a single transaction
  • Affects squashed changeset push + pull via iModelHub
  • Conflict resolution strategy: OR SKIP / unique index violation handling

@khanaffan khanaffan changed the title [DONOT MERGE] CodeValue swap is captured by changeset but ignored on pull changes and undo CodeValue swap is captured by changeset but ignored on pull changes and undo (Test to reproduce it) May 12, 2026
@ramanujam-raman
Copy link
Copy Markdown

ramanujam-raman commented May 13, 2026

Thanks for explaning this to me Affan. From what I understand -

  • This will manifest as an error when any column with a unique constraint have values in two rows swapped out.
    • The swap could be executed by setting it to an intermediate NULL value (can be any arbitrarily unique temporary value, but NULL-s are considered unique by Sqlite so that's a typical pattern), or by controlling other columns that make up the unique constraint index (where that's applicable - e.g., Code).
  • Such a swap would then manifest as an error in any situtation where individual changes have been aggregated in an "uncontrolled" way. i.e., two or more UPDATE-s have merged losing an intermediate update and the sequence of operations. So this (unexpected reliance on apply sequence) could happen in one of these scenarios -
    • Any individual Txn that spans the swap (undo-redo scenario)
    • Any changeset that merges Txn-s that span the swap (apply/reverse changeset)
    • Any aggregation of changeset-s that span the swap (not a typical usecase, but such a case in fact led us to investigate this!)
  • The specific problem doesn't affect 'reversibility' of Txns/Changesets - i.e., the problem won't manifest in only the reverse direction - if it happens, it would've already happened in the forward apply.
    • So reverse after an apply of a change set will not be affected.
    • But undo after modifying a briefcase could be affected, since we make the changes to the briefcase directly (step-by-step) in the forward direction.

@khanaffan khanaffan changed the title CodeValue swap is captured by changeset but ignored on pull changes and undo (Test to reproduce it) Code swap does not take effect when second user pulls changes May 15, 2026
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.

2 participants