{"record":{"id":"78c819c22bbb583a","repo":"remix-run/remix","slug":"upsert-requires-at-least-one-value-78c819","errorCode":null,"errorMessage":"upsert requires at least one value","messagePattern":"upsert requires at least one value","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/data-table-sqlite/src/lib/sql-compiler.ts","lineNumber":212,"sourceCode":"              .map((column) => {\n                let value = Object.prototype.hasOwnProperty.call(row, column) ? row[column] : null\n                return pushValue(context, value)\n              })\n              .join(', ') +\n            ')',\n        )\n        .join(', ') +\n      compileReturningClause(returning),\n    values: context.values,\n  }\n}\n\nfunction compileUpsertOperation(operation: UpsertOperation, context: CompileContext): SqlStatement {\n  let insertColumns = Object.keys(operation.values)\n  let conflictTarget = operation.conflictTarget ?? [...getTablePrimaryKey(operation.table)]\n\n  if (insertColumns.length === 0) {\n    throw new Error('upsert requires at least one value')\n  }\n\n  let updateValues = operation.update ?? operation.values\n  let updateColumns = Object.keys(updateValues)\n\n  let conflictClause = ''\n\n  if (updateColumns.length === 0) {\n    conflictClause =\n      ' on conflict (' +\n      conflictTarget.map((column: string) => quotePath(column)).join(', ') +\n      ') do nothing'\n  } else {\n    conflictClause =\n      ' on conflict (' +\n      conflictTarget.map((column: string) => quotePath(column)).join(', ') +\n      ') do update set ' +\n      updateColumns","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/data-table-sqlite/src/lib/sql-compiler.ts#L194-L230","documentation":"compileUpsertOperation requires the values object of an upsert operation to contain at least one column. An upsert with zero keys would generate an INSERT with no columns, which is invalid SQL, so the compiler rejects it early with a descriptive message.","triggerScenarios":"Calling upsert-like APIs with values = {} or an object built from spread/loop that ends up empty, e.g. inserting {} or Object.fromEntries([]).","commonSituations":"Building values dynamically (e.g. sanitizing input strips every key, or mapping an empty array to an object) and passing the empty result to upsert; defaulting to {} when optional payload fields are missing.","solutions":["Validate that the values object has at least one key before calling upsert and skip or return a 400 otherwise.","Fix the upstream construction (spread of empty object, filtered mapping) so it never produces an empty payload."],"exampleFix":"// before\nawait query.upsert(table, values /* {} */)\n\n// after\nif (Object.keys(values).length === 0) {\n  throw new Response('Empty payload', { status: 400 })\n}\nawait query.upsert(table, values)","handlingStrategy":"validation","validationCode":"if (Object.keys(values).length === 0) {\n  throw new Response('Payload must include at least one column', { status: 400 })\n}\nawait query.upsert(table, values)","typeGuard":"function hasUpsertValues(values: Record<string, unknown>): boolean {\n  return Object.keys(values).length > 0\n}","tryCatchPattern":null,"preventionTips":["Validate dynamic payloads before upsert.","Unit-test the empty-payload edge case in handlers that spread optional fields."],"tags":["sqlite","upsert","validation","sql-compiler"],"backgroundTag":"empty-payload-validation","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}