Environment
- hugr v0.3.37,
ghcr.io/hugr-lab/automigrate:latest
- Data source:
type: "postgres", self_defined: true, writable
Reproduce
CREATE TABLE customers (id text PRIMARY KEY, company_name text);
INSERT INTO customers VALUES ('ALFKI', 'Alfreds Futterkiste'), ('BONAP', 'Bon app');
mutation {
scratch {
update_customers(
filter: {id: {eq: "ALFKI"}}
data: {company_name: "Alfreds Futterkiste 2"}
) { affected_rows last_id success message }
}
}
Actual
{"update_customers": {"affected_rows": 0, "last_id": 0, "success": true, "message": "success"}}
The row IS updated in PostgreSQL (verified directly). delete_customers behaves the same: row deleted, affected_rows: 0.
A mutation whose filter matches nothing returns exactly the same OperationResult, so callers cannot distinguish "updated 1 row" from "updated nothing".
Root cause (from reading the code)
pkg/planner/node_update.go (same in node_delete.go): the result is built from the driver's exec result —
res, err := db.Exec(ctx, node.plan.CompiledQuery, ...)
...
case "affected_rows":
r, _ := res.RowsAffected()
For engines implementing EngineQueryScanner (postgres, mssql) the statement is wrapped in CALL postgres_execute('db', 'UPDATE ...'). A CALL returns no changed-rows count through the DuckDB driver, so RowsAffected() is always 0. success and message are hardcoded ("true" / "success"), and LastInsertId() is not supported by the driver, so the whole OperationResult carries no real information for pushdown-executed mutations.
A possible fix: have postgres_execute (or a wrapper query) select the affected-row count — e.g. execute WITH r AS (UPDATE ... RETURNING 1) SELECT COUNT(*) FROM r via postgres_query — and populate OperationResult from that result instead of the driver metadata.
Environment
ghcr.io/hugr-lab/automigrate:latesttype: "postgres",self_defined: true, writableReproduce
Actual
{"update_customers": {"affected_rows": 0, "last_id": 0, "success": true, "message": "success"}}The row IS updated in PostgreSQL (verified directly).
delete_customersbehaves the same: row deleted,affected_rows: 0.A mutation whose filter matches nothing returns exactly the same OperationResult, so callers cannot distinguish "updated 1 row" from "updated nothing".
Root cause (from reading the code)
pkg/planner/node_update.go(same innode_delete.go): the result is built from the driver's exec result —For engines implementing
EngineQueryScanner(postgres, mssql) the statement is wrapped inCALL postgres_execute('db', 'UPDATE ...'). ACALLreturns no changed-rows count through the DuckDB driver, soRowsAffected()is always 0.successandmessageare hardcoded ("true"/"success"), andLastInsertId()is not supported by the driver, so the wholeOperationResultcarries no real information for pushdown-executed mutations.A possible fix: have
postgres_execute(or a wrapper query) select the affected-row count — e.g. executeWITH r AS (UPDATE ... RETURNING 1) SELECT COUNT(*) FROM rviapostgres_query— and populateOperationResultfrom that result instead of the driver metadata.