Skip to Content
We are live but in Staging 🎉

Merge

Upsert via match-columns. K3 will INSERT rows that don’t exist (by match_columns) and UPDATE (or DELETE) those that do, per when_matched / when_not_matched actions. See the Data hub for the full RPC list.

RPCHTTP
MergePOST /:bucket/tables/:table_name/merge

gRPC setup — grpcurl, endpoints, reflection, and field-name casing — is covered once in Conventions → Using gRPC.

Merge

Request

Upsert by composite key:

curl -sS -X POST "https://k3.dev.dodil.io/kb-prod/tables/events/merge" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "bucket": "kb-prod", "tableName": "events", "rows": [ "{\"id\":1,\"user_id\":\"u-101\",\"event_type\":\"click_pricing\",\"payload\":{\"page\":\"/pricing\",\"variant\":\"B\"}}", "{\"id\":4,\"user_id\":\"u-103\",\"event_type\":\"signup\",\"payload\":{\"plan\":\"pro\"}}" ], "matchColumns": ["id", "user_id"], "whenMatched": "update", "whenNotMatched": "insert" }'

Delete-on-match:

curl -sS -X POST "https://k3.dev.dodil.io/kb-prod/tables/events/merge" \ -H "Authorization: Bearer $DODIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "bucket": "kb-prod", "tableName": "events", "rows": ["{\"id\":5,\"user_id\":\"u-104\"}"], "matchColumns": ["id", "user_id"], "whenMatched": "delete", "whenNotMatched": "ignore" }'

Response

{ "rowsWritten": "2", "version": "0", "pendingDrain": true }

rows_written counts source rows accepted into the write log — regardless of whether each row will ultimately MATCH (update) or NOT MATCH (insert) on drain. The pre-drain breakdown is genuinely unknowable; the post-drain insert/update/delete counts surface via DescribeTable.last_drain_*.

See also