Skip to content

[PowerSync] collection.update throws "Could not find schema for <column> column" when a synced row has a column the client schema doesn't declare #1818

Description

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

@tanstack/powersync-db-collection 0.1.67 (latest), @tanstack/db 0.9.0, @powersync/common 1.57.3, @powersync/node 0.19.5. The relevant code is unchanged on main in both repositories.

Describe the bug

PowerSync syncs rows as schemaless JSON and applies the client schema as views, so a backend can add a column before every installed app version knows about it — older clients simply don't see the new column. With the collection, that column breaks writes instead:

  1. The collection creates its diff trigger without a columns list, so TriggerManagerImpl records the raw NEW.data ("Track all columns"), including keys the client schema does not declare.
  2. When a row changes, the collection deserializes that JSON into memory, extra key included. (Rows loaded by the initial SELECT * FROM <view> don't carry it, so rows that never change are unaffected.)
  3. collection.update serializes the in-memory row with serializeForSQLite, which throws Could not find schema for <key> column. for the undeclared key.

This is the normal situation for mobile apps, where older versions stay installed while the backend moves on: one backend migration and every update on a changed row fails for those users.

To Reproduce

A local @powersync/node database. Writing the synced JSON in ps_data__todos directly stands in for the sync client applying a row from a newer backend; we first hit this with a real PowerSync service.

import { mkdtempSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { PowerSyncDatabase, Schema, Table, column } from '@powersync/node'
import { createCollection } from '@tanstack/db'
import { powerSyncCollectionOptions } from '@tanstack/powersync-db-collection'

// This app version's schema: no `priority` column.
const AppSchema = new Schema({ todos: new Table({ title: column.text }) })

const db = new PowerSyncDatabase({
  schema: AppSchema,
  database: { dbFilename: 'repro.sqlite', dbLocation: mkdtempSync(join(tmpdir(), 'ps-undeclared-')) },
})
await db.init()
await db.execute(`INSERT INTO todos (id, title) VALUES ('t1', 'Write report')`)

const todos = createCollection(powerSyncCollectionOptions({ database: db, table: AppSchema.props.todos }))
await todos.preload()

// A newer backend adds `priority`; the synced row now carries it.
await db.writeTransaction(async (tx) => {
  await tx.execute(`UPDATE ps_data__todos SET data = json_set(data, '$.priority', 'high') WHERE id = 't1'`)
})
await new Promise((r) => setTimeout(r, 200)) // let the collection flush the change into memory

console.log('collection row keys:', Object.keys(todos.get('t1') ?? {}).filter((k) => !k.startsWith('$')))
console.log('view row:', await db.get(`SELECT * FROM todos WHERE id = 't1'`))

try {
  const tx = todos.update('t1', (draft) => {
    draft.title = 'Write report today'
  })
  await tx.isPersisted.promise
  console.log('OK: update persisted')
} catch (err) {
  console.log(`BUG: ${(err as Error).message}`)
}

Output:

collection row keys: [ 'id', 'title', 'priority' ]
view row: { id: 't1', title: 'Write report' }
BUG: Could not find schema for priority column.

Expected behavior

The collection ignores columns the schema does not declare, as the view does, and the update persists.

Additional context

Passing the declared column names to createDiffTrigger (it accepts a columns option) or dropping undeclared keys when a sync row is deserialized would avoid it. Persisting only mutation.changes in handleUpdate (#1817, a lost-update problem) would also stop this particular throw, but the undeclared keys would still show up in query results.

Environment: Node 22.22.1 on macOS 26.6 (arm64).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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