{"id":"46af3f5685f7f02d","repo":"knex/knex","slug":"onconflict-is-not-supported-for-oracledb","errorCode":null,"errorMessage":".onConflict() is not supported for oracledb.","messagePattern":"\\.onConflict\\(\\) is not supported for oracledb\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/dialects/oracle/query/oracle-querycompiler.js","lineNumber":38,"sourceCode":"  'group',\n  'having',\n  'order',\n  'lock',\n];\n\n// Query Compiler\n// -------\n\n// Set the \"Formatter\" to use for the queries,\n// ensuring that all parameterized values (even across sub-queries)\n// are properly built into the same query.\nclass QueryCompiler_Oracle extends QueryCompiler {\n  constructor(client, builder, formatter) {\n    super(client, builder, formatter);\n\n    const { onConflict } = this.single;\n    if (onConflict) {\n      throw new Error('.onConflict() is not supported for oracledb.');\n    }\n\n    // Compiles the `select` statement, or nested sub-selects\n    // by calling each of the component compilers, trimming out\n    // the empties, and returning a generated query string.\n    this.first = this.select;\n  }\n\n  // Compiles an \"insert\" query, allowing for multiple\n  // inserts using a single query statement.\n  insert() {\n    let insertValues = this.single.insert || [];\n    let { returning } = this.single;\n\n    if (!Array.isArray(insertValues) && isPlainObject(this.single.insert)) {\n      insertValues = [this.single.insert];\n    }\n","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/knex/knex/blob/e25d54bcb707714a17f5a5744eba5c4246bb4d1d/lib/dialects/oracle/query/oracle-querycompiler.js#L20-L56","documentation":"QueryCompiler_Oracle's constructor inspects this.single.onConflict and immediately throws if set. The legacy oracle dialect never implemented ON CONFLICT upserts, so any onConflict in the builder fails fast at compile time. Note the message says 'oracledb' even though this lives in the oracle dialect folder. Thrown the moment the compiler is constructed for the query.","triggerScenarios":"Using .onConflict(column) (in any chaining form — ignore, merge, or with updates) on a query built against the legacy 'oracle' client. The throw happens before any merge/ignore logic since it checks onConflict presence directly.","commonSituations":"Switching a codebase from Postgres to Oracle and assuming onConflict is portable; shared model layer that uses upserts; migrating from the oracledb dialect (which may handle some cases) to the older oracle driver.","solutions":["Switch the client to 'oracledb' (the modern driver) which has broader upsert support, or","Rewrite the upsert using Oracle's MERGE INTO ... WHEN MATCHED via knex.raw.","Branch the upsert code path by dialect so oracle uses MERGE instead of onConflict."],"exampleFix":"// before\nawait knex('users')\n  .insert({ id: 1, email: 'a@b.c' })\n  .onConflict('id').merge();\n// after (Oracle MERGE)\nawait knex.raw(`\n  MERGE INTO users d\n  USING (SELECT 1 AS id, 'a@b.c' AS email FROM dual) s\n  ON (d.id = s.id)\n  WHEN MATCHED THEN UPDATE SET d.email = s.email\n  WHEN NOT MATCHED THEN INSERT (id, email) VALUES (s.id, s.email)\n`);","handlingStrategy":"validation","validationCode":"// Reject onConflict at build time for the oracle dialect with a helpful message.\nfunction assertNoOnConflictForOracle(knex) {\n  if (knex.client.driverName === 'oracle') {\n    throw new Error('onConflict is unsupported on the legacy oracle driver — use MERGE INTO or the oracledb client.');\n  }\n}\n// call before .onConflict in shared upsert helpers","typeGuard":"function supportsOnConflict(driverName) {\n  return !['oracle'].includes(driverName);\n}","tryCatchPattern":"try {\n  await knex('users').insert(row).onConflict('id').merge();\n} catch (e) {\n  if (/onConflict\\(\\) is not supported for oracledb/i.test(e.message)) {\n    await knex.raw(`MERGE INTO users d USING (SELECT :id AS id, :email AS email FROM dual) s ON (d.id = s.id) WHEN MATCHED THEN UPDATE SET d.email = s.email WHEN NOT MATCHED THEN INSERT (id, email) VALUES (s.id, s.email)`, row);\n  } else throw e;\n}","preventionTips":["Prefer the modern 'oracledb' client over legacy 'oracle'.","Implement upserts via MERGE INTO for Oracle instead of onConflict.","Branch shared query helpers by dialect."],"tags":["oracle","on-conflict","upsert","dialect-feature-gap"],"analyzedSha":"e25d54bcb707714a17f5a5744eba5c4246bb4d1d","analyzedAt":"2026-08-03T18:35:32.148Z","schemaVersion":2}