diff --git a/docs/changelog/143449.yaml b/docs/changelog/143449.yaml new file mode 100644 index 0000000000000..016b384e43a0e --- /dev/null +++ b/docs/changelog/143449.yaml @@ -0,0 +1,5 @@ +pr: 143449 +summary: Fix CSV-escaped quotes rendering in generated ES|QL docs examples +area: ES|QL +type: bug +issues: [] diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/json_extract.md b/docs/reference/query-languages/esql/_snippets/functions/examples/json_extract.md index b3f27fe775896..ae41c037feec7 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/examples/json_extract.md +++ b/docs/reference/query-languages/esql/_snippets/functions/examples/json_extract.md @@ -9,7 +9,7 @@ ROW log = """{"severity":"ERROR","body":"Payment processing failed"}""" | log:keyword | severity:keyword | | --- | --- | -| "{""severity"":""ERROR"",""body"":""Payment processing failed""}" | ERROR | +| {"severity":"ERROR","body":"Payment processing failed"} | ERROR | The `$` prefix is optional — this query produces the same result as the previous example: @@ -20,7 +20,7 @@ ROW log = """{"severity":"ERROR","body":"Payment processing failed"}""" | log:keyword | severity:keyword | | --- | --- | -| "{""severity"":""ERROR"",""body"":""Payment processing failed""}" | ERROR | +| {"severity":"ERROR","body":"Payment processing failed"} | ERROR | To extract a deeply nested value, use dot-notation: @@ -31,7 +31,7 @@ ROW log = """{"resource":{"service":{"name":"order-service"}}}""" | log:keyword | svc:keyword | | --- | --- | -| "{""resource"":{""service"":{""name"":""order-service""}}}" | order-service | +| {"resource":{"service":{"name":"order-service"}}} | order-service | Keys that contain dots (common in OpenTelemetry semantic conventions) require quoted bracket notation — here `service.name` is a single key, not a nested path: @@ -58,7 +58,7 @@ ROW log = """{"spans":[{"name":"auth","duration":12},{"name":"db-query","duratio | log:keyword | span:keyword | | --- | --- | -| "{""spans"":[{""name"":""auth"",""duration"":12},{""name"":""db-query"",""duration"":45}]}" | db-query | +| {"spans":[{"name":"auth","duration":12},{"name":"db-query","duration":45}]} | db-query | When the extracted value is an object or array, it is returned as a JSON string: @@ -69,7 +69,7 @@ ROW log = """{"resource":{"service.name":"api-gateway","host.name":"api-server-0 | log:keyword | resource:keyword | | --- | --- | -| "{""resource"":{""service.name"":""api-gateway"",""host.name"":""api-server-03""},""severity"":""INFO""}" | "{""service.name"":""api-gateway"",""host.name"":""api-server-03""}" | +| {"resource":{"service.name":"api-gateway","host.name":"api-server-03"},"severity":"INFO"} | {"service.name":"api-gateway","host.name":"api-server-03"} | To extract from a top-level JSON array, use a bracket index on the root element: diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/to_tdigest.md b/docs/reference/query-languages/esql/_snippets/functions/examples/to_tdigest.md index dad4698c92571..731e6683ac2e1 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/examples/to_tdigest.md +++ b/docs/reference/query-languages/esql/_snippets/functions/examples/to_tdigest.md @@ -12,6 +12,6 @@ FROM histogram_standard_index | responseTime:histogram | tdigest:tdigest | | --- | --- | -| "{""values"":[0.1,0.2,0.3,0.4,0.5], ""counts"":[3,7,23,12,6]}" | "{""min"": 0.1, ""max"": 0.5, ""sum"": 16.4, ""centroids"":[0.1,0.2,0.3,0.4,0.5], ""counts"":[3,7,23,12,6]}" | +| {"values":[0.1,0.2,0.3,0.4,0.5], "counts":[3,7,23,12,6]} | {"min": 0.1, "max": 0.5, "sum": 16.4, "centroids":[0.1,0.2,0.3,0.4,0.5], "counts":[3,7,23,12,6]} | diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3Support.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3Support.java index f067bc654d2ad..c654ec50db426 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3Support.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3Support.java @@ -1896,8 +1896,17 @@ private String renderTableLine(String[] columns) { if (i > 0) { sb.append(" | "); } + String cell = columns[i].trim(); + // Unescape RFC 4180 CSV-quoted values that contain doubled quotes (""). + // In CSV spec results, a value like "{""key"":""val""}" uses outer " as field delimiters + // and "" as escaped ". We strip the delimiters and unescape so JSON renders correctly + // in docs (e.g. {"key":"value"} instead of {""key"":""value""}). + // Simple quoted values like "POINT(...)" have no "" inside and are left unchanged. + if (cell.startsWith("\"") && cell.endsWith("\"") && cell.substring(1, cell.length() - 1).contains("\"\"")) { + cell = cell.substring(1, cell.length() - 1).replace("\"\"", "\""); + } // Some cells have regex content (see CATEGORIZE), so we need to escape this - sb.append(columns[i].trim().replaceAll("\\.\\*", ".\\\\*")); + sb.append(cell.replaceAll("\\.\\*", ".\\\\*")); } return sb.append(" |\n").toString(); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3SupportTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3SupportTests.java index 4b3775f8c3dbf..0fb07be3cf740 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3SupportTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/DocsV3SupportTests.java @@ -213,6 +213,34 @@ public void testRenderingExampleResultRaw() throws IOException { assertThat(results, equalTo(expectedResults)); } + /** + * Verify that RFC 4180 CSV-quoted values with doubled quotes are properly unescaped, + * so JSON strings render correctly in docs instead of showing {""key"":""value""}. + */ + public void testRenderingExampleResultCsvJsonUnescaping() throws IOException { + String expectedResults = """ + | log:keyword | severity:keyword | + | --- | --- | + | {"severity":"ERROR","body":"Payment processing failed"} | ERROR | + """; + String results = docs.loadExample("json_extract.csv-spec", "json_extract-result"); + assertThat(results, equalTo(expectedResults)); + } + + /** + * Verify that simple quoted values (no doubled quotes inside) are preserved as-is. + * Only values with "" (RFC 4180 escaping) should be unescaped. + */ + public void testRenderingExampleResultSimpleQuotesPreserved() throws IOException { + String expectedResults = """ + | wkt:keyword | pt:geo_point | + | --- | --- | + | "POINT(42.97109630194 14.7552534413725)" | POINT(42.97109630194 14.7552534413725) | + """; + String results = docs.loadExample("spatial.csv-spec", "to_geopoint-str-result"); + assertThat(results, equalTo(expectedResults)); + } + public void testRenderingExampleRaw2() throws IOException { String expectedExample = """ ROW n=1