{"record":{"id":"a2e0fd31b9c19639","repo":"phalcon/cphalcon","slug":"on-conflict-do-update-requires-at-least-one-update","errorCode":null,"errorMessage":"ON CONFLICT DO UPDATE requires at least one update column","messagePattern":"ON CONFLICT DO UPDATE requires at least one update column","errorType":"exception","errorClass":"ConflictUpdateColumnRequired","httpStatus":null,"severity":"error","filePath":"phalcon/Db/Dialect.zep","lineNumber":553,"sourceCode":"\n    /**\n     * Appends an `ON CONFLICT (col, ...) DO UPDATE SET col = excluded.col`\n     * upsert clause to the supplied INSERT statement. The syntax is the\n     * SQL standard form recognized by PostgreSQL (9.5+) and SQLite (3.24+).\n     * MySQL overrides this method to throw because its `ON DUPLICATE KEY\n     * UPDATE` has a different shape (deferred to parser item #23).\n     */\n    public function onConflictUpdate( string sqlQuery,  array conflictColumns,  array updateColumns) -> string\n    {\n        var col;\n        array assignments;\n\n        if unlikely empty conflictColumns {\n            throw new ConflictTargetColumnRequired();\n        }\n\n        if unlikely empty updateColumns {\n            throw new ConflictUpdateColumnRequired();\n        }\n\n        let assignments = [];\n        for col in updateColumns {\n            let assignments[] = this->escape((string) col)\n                . \" = excluded.\" . this->escape((string) col);\n        }\n\n        return sqlQuery\n            . \" ON CONFLICT (\" . this->getColumnList(conflictColumns) . \")\"\n            . \" DO UPDATE SET \" . implode(\", \", assignments);\n    }\n\n    /**\n     * Returns a SQL statement extended with a `RETURNING` clause so the\n     * INSERT/UPDATE/DELETE returns rows. Supported by PostgreSQL and\n     * SQLite 3.35+. Pass `[\"*\"]` for `RETURNING *`, or a list of column\n     * names. The base implementation throws - MySQL inherits it because","sourceCodeStart":535,"sourceCodeEnd":571,"githubUrl":"https://github.com/phalcon/cphalcon/blob/b7419de9cd0a8a3f48441ead84c9f8415d463e25/phalcon/Db/Dialect.zep#L535-L571","documentation":"Dialect::onConflictUpdate() requires a non-empty updateColumns list: those columns become the `DO UPDATE SET col = excluded.col` assignments. An empty list means the upsert would do nothing on conflict, so the method throws ConflictUpdateColumnRequired after the conflict-target check.","triggerScenarios":"Calling $dialect->onConflictUpdate($insertSql, ['id'], []) - e.g. computing update columns as array_diff($columns, $conflictColumns) when every column is part of the key; passing column names in the first array only.","commonSituations":"Upsert helpers deriving update columns from insert columns minus key columns, where the table has no non-key columns; config-driven upsert specs with an empty update list; argument-order mixups.","solutions":["Pass at least one column to update on conflict, e.g. ['updated_at'] or the mutable fields.","If nothing should change on conflict, do not use DO UPDATE - use ON CONFLICT DO NOTHING (plain SQL) instead.","Validate count($updateColumns) > 0 in your upsert wrapper before calling the dialect."],"exampleFix":"// before\n$update = array_diff($allColumns, ['sku']); // [] when table has only the key column\n$sql = $dialect->onConflictUpdate($insertSql, ['sku'], $update); // throws ConflictUpdateColumnRequired\n\n// after\n$update = array_values(array_diff($allColumns, ['sku']));\nif ($update === []) {\n    $sql = $insertSql . ' ON CONFLICT (sku) DO NOTHING';\n} else {\n    $sql = $dialect->onConflictUpdate($insertSql, ['sku'], $update);\n}","handlingStrategy":"validation","validationCode":"if (count($updateColumns) === 0) {\n    throw new InvalidArgumentException('ON CONFLICT DO UPDATE requires update columns');\n}\n$sql = $dialect->onConflictUpdate($insertSql, $conflictColumns, $updateColumns);","typeGuard":"function hasUpdateColumns(array $updateColumns): bool\n{\n    return $updateColumns !== []\n        && array_filter($updateColumns, 'is_string') !== [];\n}","tryCatchPattern":null,"preventionTips":["If nothing should change on conflict, emit ON CONFLICT DO NOTHING instead of DO UPDATE.","When deriving update columns as insert-columns minus key columns, handle the empty case explicitly.","Validate both upsert arrays in a wrapper before calling the dialect."],"tags":["upsert","on-conflict","dialect","validation","phalcon-db"],"backgroundTag":"upsert-missing-update-columns","analyzedSha":"b7419de9cd0a8a3f48441ead84c9f8415d463e25","analyzedAt":"2026-08-21T06:21:18.811Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}