From a804bd8dbe87c6af3dacb0103c15a99edb60e0a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Jan 2026 23:08:29 +0000 Subject: [PATCH 1/3] Add integration tests for columnMapper.encode in loadSubset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two tests to verify that columnMapper.encode is properly applied when compiling WHERE clauses for loadSubset requests: 1. When columnMapper.encode is configured, column names in WHERE clauses are transformed (e.g., isArchived → is_archived) 2. When no columnMapper is provided, column names are preserved as-is This helps verify the fix from PR #1141 that added columnMapper support to SQL compilation for subset queries. --- .../tests/electric.test.ts | 122 +++++++++++++++++- 1 file changed, 117 insertions(+), 5 deletions(-) diff --git a/packages/electric-db-collection/tests/electric.test.ts b/packages/electric-db-collection/tests/electric.test.ts index 7a8bc7384..a47955981 100644 --- a/packages/electric-db-collection/tests/electric.test.ts +++ b/packages/electric-db-collection/tests/electric.test.ts @@ -5,18 +5,28 @@ import { createTransaction, } from '@tanstack/db' import { electricCollectionOptions, isChangeMessage } from '../src/electric' -import type { ElectricCollectionUtils } from '../src/electric' -import type { - Collection, +import type { Collection, + IR, InsertMutationFnParams, MutationFnParams, PendingMutation, Transaction, - TransactionWithMutations, -} from '@tanstack/db' + TransactionWithMutations } from '@tanstack/db' +import type { ElectricCollectionUtils } from '../src/electric' import type { Message, Row } from '@electric-sql/client' import type { StandardSchemaV1 } from '@standard-schema/spec' +// Helper functions for creating IR expressions (same as sql-compiler.test.ts) +function val(value: T): IR.BasicExpression { + return { type: `val`, value } as IR.BasicExpression +} +function ref(...path: Array): IR.BasicExpression { + return { type: `ref`, path } as IR.BasicExpression +} +function func(name: string, args: Array): IR.BasicExpression { + return { type: `func`, name, args } as IR.BasicExpression +} + // Mock the ShapeStream module const mockSubscribe = vi.fn() const mockRequestSnapshot = vi.fn() @@ -2842,6 +2852,108 @@ describe(`Electric Integration`, () => { }), ) }) + + it(`should encode column names using columnMapper.encode in loadSubset WHERE clause`, async () => { + vi.clearAllMocks() + + // Helper to convert camelCase to snake_case (simulating snakeCamelMapper's encode) + const camelToSnake = (str: string): string => + str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`) + + const config = { + id: `column-mapper-encode-test`, + shapeOptions: { + url: `http://test-url`, + params: { + table: `test_table`, + }, + // Configure columnMapper to convert camelCase to snake_case + columnMapper: { + encode: camelToSnake, + decode: (name: string) => name, // Not used in this test + }, + }, + syncMode: `on-demand` as const, + getKey: (item: Row) => item.id as number, + startSync: true, + } + + const testCollection = createCollection(electricCollectionOptions(config)) + + // Send up-to-date to mark collection as ready + subscriber([ + { + headers: { control: `up-to-date` }, + }, + ]) + + // Call loadSubset with a WHERE clause using camelCase column names + // The column names should be encoded to snake_case + await testCollection._sync.loadSubset({ + where: func(`and`, [ + func(`eq`, [ref(`isArchived`), val(false)]), + func(`eq`, [ref(`isDeleted`), val(false)]), + ]), + limit: 10, + }) + + // Verify requestSnapshot was called + expect(mockRequestSnapshot).toHaveBeenCalled() + + // Get the params that were passed to requestSnapshot + const callArgs = mockRequestSnapshot.mock.calls[0]![0] + + // The WHERE clause should have snake_case column names + // The format is: ("column_name" = $1) + expect(callArgs.where).toContain(`"is_archived"`) + expect(callArgs.where).toContain(`"is_deleted"`) + + // Should NOT contain the original camelCase names + expect(callArgs.where).not.toContain(`"isArchived"`) + expect(callArgs.where).not.toContain(`"isDeleted"`) + }) + + it(`should not encode column names when columnMapper is not provided`, async () => { + vi.clearAllMocks() + + const config = { + id: `no-column-mapper-test`, + shapeOptions: { + url: `http://test-url`, + params: { + table: `test_table`, + }, + // No columnMapper configured + }, + syncMode: `on-demand` as const, + getKey: (item: Row) => item.id as number, + startSync: true, + } + + const testCollection = createCollection(electricCollectionOptions(config)) + + // Send up-to-date to mark collection as ready + subscriber([ + { + headers: { control: `up-to-date` }, + }, + ]) + + // Call loadSubset with a WHERE clause using camelCase column names + await testCollection._sync.loadSubset({ + where: func(`eq`, [ref(`isArchived`), val(false)]), + limit: 10, + }) + + // Verify requestSnapshot was called + expect(mockRequestSnapshot).toHaveBeenCalled() + + // Get the params that were passed to requestSnapshot + const callArgs = mockRequestSnapshot.mock.calls[0]![0] + + // The WHERE clause should preserve camelCase names when no columnMapper + expect(callArgs.where).toContain(`"isArchived"`) + }) }) // Tests for overlapping subset queries with duplicate keys From f783ff1c9e36163f73fd09c3eab0f5ab4438daea Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Jan 2026 23:13:26 +0000 Subject: [PATCH 2/3] Add debug logging for columnMapper.encode diagnostics Adds debug logging to help diagnose when columnMapper encoding is or isn't being applied to WHERE clauses in loadSubset requests. Logs: - Whether columnMapper.encode is configured when createLoadSubsetDedupe runs - The compiled WHERE clause before it's sent to the server Enable debugging with: DEBUG=ts/db:electric --- packages/electric-db-collection/src/electric.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/electric-db-collection/src/electric.ts b/packages/electric-db-collection/src/electric.ts index 994889ded..45256a779 100644 --- a/packages/electric-db-collection/src/electric.ts +++ b/packages/electric-db-collection/src/electric.ts @@ -374,11 +374,18 @@ function createLoadSubsetDedupe>({ const compileOptions = encodeColumnName ? { encodeColumnName } : undefined + debug( + `${collectionId ? `[${collectionId}] ` : ``}createLoadSubsetDedupe: columnMapper.encode is ${encodeColumnName ? `configured` : `NOT configured`}`, + ) + const loadSubset = async (opts: LoadSubsetOptions) => { // In progressive mode, use fetchSnapshot during snapshot phase if (isBufferingInitialSync()) { // Progressive mode snapshot phase: fetch and apply immediately const snapshotParams = compileSQL(opts, compileOptions) + debug( + `${collectionId ? `[${collectionId}] ` : ``}loadSubset compiled WHERE: ${snapshotParams.where}`, + ) try { const { data: rows } = await stream.fetchSnapshot(snapshotParams) @@ -466,6 +473,9 @@ function createLoadSubsetDedupe>({ } else { // No cursor - standard single request const snapshotParams = compileSQL(opts, compileOptions) + debug( + `${collectionId ? `[${collectionId}] ` : ``}loadSubset compiled WHERE: ${snapshotParams.where}`, + ) await stream.requestSnapshot(snapshotParams) } } From 365807dc13aaae08d4669873f92c32f17565b1e7 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 22 Jan 2026 23:24:57 +0000 Subject: [PATCH 3/3] ci: apply automated fixes --- packages/electric-db-collection/tests/electric.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/electric-db-collection/tests/electric.test.ts b/packages/electric-db-collection/tests/electric.test.ts index a47955981..06c25399c 100644 --- a/packages/electric-db-collection/tests/electric.test.ts +++ b/packages/electric-db-collection/tests/electric.test.ts @@ -5,13 +5,15 @@ import { createTransaction, } from '@tanstack/db' import { electricCollectionOptions, isChangeMessage } from '../src/electric' -import type { Collection, +import type { + Collection, IR, InsertMutationFnParams, MutationFnParams, PendingMutation, Transaction, - TransactionWithMutations } from '@tanstack/db' + TransactionWithMutations, +} from '@tanstack/db' import type { ElectricCollectionUtils } from '../src/electric' import type { Message, Row } from '@electric-sql/client' import type { StandardSchemaV1 } from '@standard-schema/spec'