diff --git a/internal/explain/format.go b/internal/explain/format.go index 3ce29f77b..0badcdf53 100644 --- a/internal/explain/format.go +++ b/internal/explain/format.go @@ -27,10 +27,12 @@ func FormatFloat(val float64) string { // escapeStringLiteral escapes special characters in a string for EXPLAIN AST output // Uses double-escaping as ClickHouse EXPLAIN AST displays strings +// Iterates over bytes to preserve raw bytes (including invalid UTF-8) func escapeStringLiteral(s string) string { var sb strings.Builder - for _, r := range s { - switch r { + for i := 0; i < len(s); i++ { + b := s[i] + switch b { case '\\': sb.WriteString("\\\\\\\\") // backslash becomes four backslashes (\\\\) case '\'': @@ -48,7 +50,7 @@ func escapeStringLiteral(s string) string { case '\f': sb.WriteString("\\\\f") // form feed becomes \\f default: - sb.WriteRune(r) + sb.WriteByte(b) } } return sb.String() diff --git a/lexer/lexer.go b/lexer/lexer.go index a8a5736b9..cf3737add 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -376,10 +376,14 @@ func (l *Lexer) readString(quote rune) Item { sb.WriteRune('\r') case '0': sb.WriteRune('\x00') + case 'a': + sb.WriteRune('\a') case 'b': sb.WriteRune('\b') case 'f': sb.WriteRune('\f') + case 'v': + sb.WriteRune('\v') case 'x': // Hex escape: \xNN l.readChar() diff --git a/parser/testdata/00342_escape_sequences/metadata.json b/parser/testdata/00342_escape_sequences/metadata.json index ef120d978..9e26dfeeb 100644 --- a/parser/testdata/00342_escape_sequences/metadata.json +++ b/parser/testdata/00342_escape_sequences/metadata.json @@ -1 +1 @@ -{"todo": true} +{} \ No newline at end of file