Summary
UPDATE <table> SET <col> = '<string-literal>' is translated into an
ingest-pipeline painless script that reads the literal as a field
reference on the doc rather than emitting a string literal. The
resulting script always assigns null to the target column, and
ignore_failure = true swallows the underlying error so the update
appears to succeed.
Repro
CREATE TABLE IF NOT EXISTS customers (
id INT,
name VARCHAR,
country KEYWORD,
PRIMARY KEY (id)
);
INSERT INTO customers (id, name, country) VALUES (1, 'Alice', 'France');
UPDATE customers SET country = 'USA' WHERE id = 1;
-- ✅ Updated 1 documents in customers. (looks successful)
SELECT id, name, country FROM customers WHERE id = 1;
-- name = 'Alice', country = NULL ← BUG: expected country = 'USA'
Evidence
The pipeline emitted by the SQL → ingest-pipeline translation for the
UPDATE above (from a CI log against ES 7.17.29 via the REST client):
SCRIPT(
description = "country ANY SCRIPT AS (USA)",
lang = "painless",
source = "def param1 = ctx.USA; ctx.country = param1",
ignore_failure = true
)
The source should be the equivalent of ctx.country = 'USA'. Instead
it reads ctx.USA — i.e. it dereferences a field named USA on the
document, which doesn't exist. param1 becomes null, then
ctx.country = null overwrites the existing value.
Impact
- Silent data corruption. The UPDATE reports success
(✅ Updated 1 documents in customers.) but the target column is
set to null. There is no error surfaced to the caller because
ignore_failure = true masks the painless undefined-field error.
- Test data setup pitfall. Found while writing test setup that
relied on UPDATE ... SET country = 'USA' WHERE id = 1 to pin a
customer's country. The follow-up query found 0 USA rows because the
field had been nulled, not updated.
- Production risk. Any user code issuing string-literal UPDATEs is
silently nulling the targeted column. Numeric and boolean literals
may be unaffected (untested).
Expected behaviour
UPDATE <table> SET <col> = '<string-literal>' WHERE <pred> should
produce a painless script that assigns the literal as a string value
to ctx.<col> — equivalent to ctx.<col> = '<literal>'.
Workaround
Use DELETE FROM <table> WHERE <pred> followed by INSERT INTO ...
with the full row. INSERT correctly serialises string literals.
Notes for the fix
- Verify whether the same bug affects other literal types (numeric,
boolean, date, null).
- Consider removing or narrowing
ignore_failure = true on
user-issued UPDATE pipelines so this class of script error stops
being silent.
Summary
UPDATE <table> SET <col> = '<string-literal>'is translated into aningest-pipeline painless script that reads the literal as a field
reference on the doc rather than emitting a string literal. The
resulting script always assigns
nullto the target column, andignore_failure = trueswallows the underlying error so the updateappears to succeed.
Repro
Evidence
The pipeline emitted by the SQL → ingest-pipeline translation for the
UPDATE above (from a CI log against ES 7.17.29 via the REST client):
The
sourceshould be the equivalent ofctx.country = 'USA'. Insteadit reads
ctx.USA— i.e. it dereferences a field namedUSAon thedocument, which doesn't exist.
param1becomesnull, thenctx.country = nulloverwrites the existing value.Impact
(
✅ Updated 1 documents in customers.) but the target column isset to
null. There is no error surfaced to the caller becauseignore_failure = truemasks the painless undefined-field error.relied on
UPDATE ... SET country = 'USA' WHERE id = 1to pin acustomer's country. The follow-up query found 0 USA rows because the
field had been nulled, not updated.
silently nulling the targeted column. Numeric and boolean literals
may be unaffected (untested).
Expected behaviour
UPDATE <table> SET <col> = '<string-literal>' WHERE <pred>shouldproduce a painless script that assigns the literal as a string value
to
ctx.<col>— equivalent toctx.<col> = '<literal>'.Workaround
Use
DELETE FROM <table> WHERE <pred>followed byINSERT INTO ...with the full row. INSERT correctly serialises string literals.
Notes for the fix
boolean, date, null).
ignore_failure = trueonuser-issued UPDATE pipelines so this class of script error stops
being silent.