From 4903693f8d787103c3a4efe7ed91dc29de04c2e5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Jan 2026 00:57:01 +0000 Subject: [PATCH 1/3] feat(db): add subtract, multiply, divide math functions Add missing math functions that were implemented in evaluators but not exported. These enable computed columns in orderBy for ranking algorithms like HN-style scoring that balances recency and rating. - Add subtract(a, b) function - Add multiply(a, b) function - Add divide(a, b) function (with null on divide-by-zero) - Export from query/index.ts - Add to operators list - Add comprehensive tests including orderBy usage --- packages/db/src/query/builder/functions.ts | 33 +++++++++ packages/db/src/query/index.ts | 3 + .../db/tests/query/builder/functions.test.ts | 72 +++++++++++++++++++ 3 files changed, 108 insertions(+) diff --git a/packages/db/src/query/builder/functions.ts b/packages/db/src/query/builder/functions.ts index 41ce11370..2fcffb1d7 100644 --- a/packages/db/src/query/builder/functions.ts +++ b/packages/db/src/query/builder/functions.ts @@ -302,6 +302,36 @@ export function add( ]) as BinaryNumericReturnType } +export function subtract( + left: T1, + right: T2, +): BinaryNumericReturnType { + return new Func(`subtract`, [ + toExpression(left), + toExpression(right), + ]) as BinaryNumericReturnType +} + +export function multiply( + left: T1, + right: T2, +): BinaryNumericReturnType { + return new Func(`multiply`, [ + toExpression(left), + toExpression(right), + ]) as BinaryNumericReturnType +} + +export function divide( + left: T1, + right: T2, +): BinaryNumericReturnType { + return new Func(`divide`, [ + toExpression(left), + toExpression(right), + ]) as BinaryNumericReturnType +} + // Aggregates export function count(arg: ExpressionLike): Aggregate { @@ -365,6 +395,9 @@ export const operators = [ `concat`, // Numeric functions `add`, + `subtract`, + `multiply`, + `divide`, // Utility functions `coalesce`, // Aggregate functions diff --git a/packages/db/src/query/index.ts b/packages/db/src/query/index.ts index 524b4dcf1..30d43c266 100644 --- a/packages/db/src/query/index.ts +++ b/packages/db/src/query/index.ts @@ -37,6 +37,9 @@ export { concat, coalesce, add, + subtract, + multiply, + divide, // Aggregates count, avg, diff --git a/packages/db/tests/query/builder/functions.test.ts b/packages/db/tests/query/builder/functions.test.ts index fb6cb4f35..3685191ae 100644 --- a/packages/db/tests/query/builder/functions.test.ts +++ b/packages/db/tests/query/builder/functions.test.ts @@ -8,6 +8,7 @@ import { coalesce, concat, count, + divide, eq, gt, gte, @@ -19,8 +20,10 @@ import { lte, max, min, + multiply, not, or, + subtract, sum, upper, } from '../../../src/query/builder/functions.js' @@ -289,5 +292,74 @@ describe(`QueryBuilder Functions`, () => { const select = builtQuery.select! expect((select.salary_plus_bonus as any).name).toBe(`add`) }) + + it(`subtract function works`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .select(({ employees }) => ({ + id: employees.id, + salary_minus_tax: subtract(employees.salary, 5000), + })) + + const builtQuery = getQueryIR(query) + const select = builtQuery.select! + expect((select.salary_minus_tax as any).name).toBe(`subtract`) + }) + + it(`multiply function works`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .select(({ employees }) => ({ + id: employees.id, + double_salary: multiply(employees.salary, 2), + })) + + const builtQuery = getQueryIR(query) + const select = builtQuery.select! + expect((select.double_salary as any).name).toBe(`multiply`) + }) + + it(`divide function works`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .select(({ employees }) => ({ + id: employees.id, + monthly_salary: divide(employees.salary, 12), + })) + + const builtQuery = getQueryIR(query) + const select = builtQuery.select! + expect((select.monthly_salary as any).name).toBe(`divide`) + }) + + it(`math functions can be combined for complex calculations`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .select(({ employees }) => ({ + id: employees.id, + // (salary * 1.1) - 500 = 10% raise minus deductions + adjusted_salary: subtract(multiply(employees.salary, 1.1), 500), + })) + + const builtQuery = getQueryIR(query) + const select = builtQuery.select! + expect((select.adjusted_salary as any).name).toBe(`subtract`) + }) + + it(`math functions can be used in orderBy`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .orderBy(({ employees }) => multiply(employees.salary, 2), `desc`) + .select(({ employees }) => ({ + id: employees.id, + salary: employees.salary, + })) + + const builtQuery = getQueryIR(query) + expect(builtQuery.orderBy).toBeDefined() + expect(builtQuery.orderBy).toHaveLength(1) + expect((builtQuery.orderBy![0]!.expression as any).name).toBe(`multiply`) + expect(builtQuery.orderBy![0]!.compareOptions.direction).toBe(`desc`) + }) }) }) From 4874663a11d7e73a09caaed54762f9446562c8b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Jan 2026 01:02:05 +0000 Subject: [PATCH 2/3] docs: document subtract, multiply, divide math functions - Add documentation for new math functions in live-queries.md - Include example of computed columns in orderBy for ranking algorithms - Add changeset for the new minor feature --- .changeset/add-math-functions.md | 25 +++++++++++++++++ docs/guides/live-queries.md | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 .changeset/add-math-functions.md diff --git a/.changeset/add-math-functions.md b/.changeset/add-math-functions.md new file mode 100644 index 000000000..4073a5291 --- /dev/null +++ b/.changeset/add-math-functions.md @@ -0,0 +1,25 @@ +--- +"@tanstack/db": patch +--- + +Add `subtract`, `multiply`, and `divide` math functions for computed columns + +These functions enable complex calculations in `select` and `orderBy` clauses, such as ranking algorithms that combine multiple factors (e.g., HN-style scoring that balances recency and rating). + +```ts +import { subtract, multiply, divide } from '@tanstack/db' + +// Example: Sort by computed ranking score +const ranked = createLiveQueryCollection((q) => + q + .from({ r: recipesCollection }) + .orderBy( + ({ r }) => subtract(multiply(r.rating, r.timesMade), divide(r.ageInMs, 86400000)), + 'desc' + ) +) +``` + +- `subtract(a, b)` - Subtraction +- `multiply(a, b)` - Multiplication +- `divide(a, b)` - Division (returns `null` on divide-by-zero) diff --git a/docs/guides/live-queries.md b/docs/guides/live-queries.md index b69689e16..89dbbc3d4 100644 --- a/docs/guides/live-queries.md +++ b/docs/guides/live-queries.md @@ -1816,12 +1816,58 @@ Add two numbers: add(user.salary, user.bonus) ``` +#### `subtract(left, right)` +Subtract two numbers: +```ts +subtract(user.salary, user.deductions) +``` + +#### `multiply(left, right)` +Multiply two numbers: +```ts +multiply(item.price, item.quantity) +``` + +#### `divide(left, right)` +Divide two numbers (returns `null` on divide-by-zero): +```ts +divide(order.total, order.itemCount) +``` + #### `coalesce(...values)` Return the first non-null value: ```ts coalesce(user.displayName, user.name, 'Unknown') ``` +#### Computed Columns in orderBy + +You can use math functions directly in `orderBy` to sort by computed values. This is useful for ranking algorithms that combine multiple factors: + +```ts +import { subtract, multiply, divide } from '@tanstack/db' + +// HN-style ranking: balance rating with recency +const rankedRecipes = createLiveQueryCollection((q) => + q + .from({ r: recipesCollection }) + .orderBy( + ({ r }) => + subtract( + multiply(r.rating, r.timesMade), // weighted rating + divide( + subtract(Date.now(), r.lastMadeAt), // time since last made + 3600000 * 24 // convert ms to days + ) + ), + 'desc' + ) + .limit(20) +) +``` + +> **Note:** When using computed expressions in `orderBy` with `limit()`, lazy loading optimization is skipped (all matching data is loaded first, then sorted). For large collections where this matters, consider pre-computing the ranking score as a stored field. + ### Aggregate Functions #### `count(value)` From 287a1ffca30dfc1adc130d2f6983e0dcf5283001 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 18 Jan 2026 05:36:19 +0000 Subject: [PATCH 3/3] ci: apply automated fixes --- .changeset/add-math-functions.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.changeset/add-math-functions.md b/.changeset/add-math-functions.md index 4073a5291..c4e70d0c5 100644 --- a/.changeset/add-math-functions.md +++ b/.changeset/add-math-functions.md @@ -1,5 +1,5 @@ --- -"@tanstack/db": patch +'@tanstack/db': patch --- Add `subtract`, `multiply`, and `divide` math functions for computed columns @@ -14,9 +14,10 @@ const ranked = createLiveQueryCollection((q) => q .from({ r: recipesCollection }) .orderBy( - ({ r }) => subtract(multiply(r.rating, r.timesMade), divide(r.ageInMs, 86400000)), - 'desc' - ) + ({ r }) => + subtract(multiply(r.rating, r.timesMade), divide(r.ageInMs, 86400000)), + 'desc', + ), ) ```