diff --git a/.changeset/five-waves-rule.md b/.changeset/five-waves-rule.md new file mode 100644 index 000000000..020f9bbba --- /dev/null +++ b/.changeset/five-waves-rule.md @@ -0,0 +1,5 @@ +--- +"@tanstack/db": patch +--- + +Fix joins using conditions with computed values (such as `concat()`) diff --git a/packages/db/src/query/compiler/joins.ts b/packages/db/src/query/compiler/joins.ts index 42687e446..50326339e 100644 --- a/packages/db/src/query/compiler/joins.ts +++ b/packages/db/src/query/compiler/joins.ts @@ -204,7 +204,12 @@ function processJoin( lazyFrom.type === `queryRef` && (lazyFrom.query.limit || lazyFrom.query.offset) - if (!limitedSubquery) { + // If join expressions contain computed values (like concat functions) + // we don't optimize the join because we don't have an index over the computed values + const hasComputedJoinExpr = + mainExpr.type === `func` || joinedExpr.type === `func` + + if (!limitedSubquery && !hasComputedJoinExpr) { // This join can be optimized by having the active collection // dynamically load keys into the lazy collection // based on the value of the joinKey and by looking up diff --git a/packages/db/tests/query/join.test.ts b/packages/db/tests/query/join.test.ts index 9b2d3ef24..dc0eca84f 100644 --- a/packages/db/tests/query/join.test.ts +++ b/packages/db/tests/query/join.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, test } from "vitest" -import { createLiveQueryCollection, eq } from "../../src/query/index.js" +import { concat, createLiveQueryCollection, eq } from "../../src/query/index.js" import { createCollection } from "../../src/collection/index.js" import { mockSyncCollectionOptions } from "../utils.js" @@ -948,6 +948,30 @@ function createJoinTests(autoIndex: `off` | `eager`): void { const alice = results.find((r) => r.user_name === `Alice`) expect(alice?.department_name).toBe(`Engineering`) }) + + test(`should handle joins with computed expressions`, () => { + const joinQuery = createLiveQueryCollection({ + startSync: true, + query: (q) => + q + .from({ user: usersCollection }) + .join( + { dept: departmentsCollection }, + ({ user, dept }) => + eq( + concat(`dept`, user.department_id), + concat(`dept`, dept.id) + ), + `inner` + ) + .select(({ user, dept }) => ({ + user_name: user.name, + department_name: dept.name, + })), + }) + + expect(joinQuery.size).toBe(3) + }) }) test(`should handle chained joins with incremental updates`, () => {