Skip to content

innerJoin drops a live-query result when one of many children is deleted #1703

Description

@samvantoever
  • I've validated the bug against the latest version of DB packages

Describe the bug

@tanstack/db 0.6.17.

I built a filtered view with a live query that lists a parent whenever it
contains at least one child matching the active filter. The query does an
innerJoin from the parent onto its matching children, which fans out one row per
matching child, then collapses those rows back to a single parent row.

When a parent has several matching children and I remove one of them, the
whole parent disappears from the view — even though its other children still match.
The parent should stay visible until the last matching child is gone.

Removing one of N (N > 1) matching children removes the parent immediately, while
N−1 matching children still remain.

To Reproduce

Verified against @tanstack/db@0.6.17. Output:

const parents = createCollection(
  localOnlyCollectionOptions<Parent>({ getKey: (p) => p.id })
);
const children = createCollection(
  localOnlyCollectionOptions<Child>({ getKey: (c) => c.id })
);

parents.insert({ id: 'P' });
// Parent P has TWO matching children.
children.insert({ id: 'C1', parentId: 'P' });
children.insert({ id: 'C2', parentId: 'P' });

// "Parents that have at least one child." The join fans out one row per child;
// keying on the parent id collapses them back to a single parent row.
const parentsWithChildren = createLiveQueryCollection({
  query: (q) =>
    q
      .from({ parent: parents })
      .innerJoin({ child: children }, ({ parent, child }) =>
        eq(parent.id, child.parentId)
      )
      .select(({ parent }) => ({ parent })),
  getKey: (row) => row.parent.id,
  startSync: true,
});

// Initial: [P]  ✅
console.log(parentsWithChildren.toArray.map((r) => r.parent.id)); // ['P']

// Remove ONE of P's two children. C2 still points at P.
children.delete('C1');

// Expected: ['P']  (C2 still matches)
// Actual (0.6.17): []  — P is dropped even though C2 remains ❌
console.log(parentsWithChildren.toArray.map((r) => r.parent.id));

Expected behavior

The parent stays in the view until its last matching child is removed.

Screenshots

N/A.

Desktop (please complete the following information):

  • OS: macOS
  • Browser: Chrome
  • Version: @tanstack/db@0.6.17

Smartphone (please complete the following information):

N/A.

Additional context

  • Happens with localOnlyCollectionOptions and with query-backed collections
    (@tanstack/query-db-collection), so it is in the join/differential layer, not a
    specific collection adapter.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions