rewriteFormat rewrites format('...literal template...', args...) with PG %-specifiers into a || concatenation. Only fires for a literal first arg containing at least one %-spec. NULL handling matches PG: %s renders NULL as empty string (coalesce), %L as the unquoted keyword NULL (quote_nullable); %I
(node *pg_query.Node, fc *pg_query.FuncCall)
| 63 | // to an error() call: DuckDB's native format would silently return the template |
| 64 | // unsubstituted, which is the exact corruption mode this transform exists to fix. |
| 65 | func (t *FunctionTransform) rewriteFormat(node *pg_query.Node, fc *pg_query.FuncCall) bool { |
| 66 | if len(fc.Args) == 0 { |
| 67 | return false |
| 68 | } |
| 69 | tmpl := extractStringConstant(fc.Args[0]) |
| 70 | if tmpl == "" || !strings.Contains(tmpl, "%") { |
| 71 | return false // non-literal or no specifier: leave for native format |
| 72 | } |
| 73 | rewriteToError := func(msg string) bool { |
| 74 | node.Node = funcCallNode("error", strConstNode(msg)).Node |
| 75 | return true |
| 76 | } |
| 77 | var segments []*pg_query.Node |
| 78 | var lit strings.Builder |
| 79 | flushLit := func() { |
| 80 | if lit.Len() > 0 { |
| 81 | segments = append(segments, strConstNode(lit.String())) |
| 82 | lit.Reset() |
| 83 | } |
| 84 | } |
| 85 | argIdx := 1 // Args[0] is the template |
| 86 | for i := 0; i < len(tmpl); i++ { |
| 87 | if tmpl[i] != '%' { |
| 88 | lit.WriteByte(tmpl[i]) |
| 89 | continue |
| 90 | } |
| 91 | if i+1 >= len(tmpl) { |
| 92 | return rewriteToError("format(): unterminated format specifier") |
| 93 | } |
| 94 | spec := tmpl[i+1] |
| 95 | i++ |
| 96 | switch spec { |
| 97 | case '%': |
| 98 | lit.WriteByte('%') |
| 99 | case 's', 'I', 'L': |
| 100 | if argIdx >= len(fc.Args) { |
| 101 | return rewriteToError("format(): too few arguments for format string") |
| 102 | } |
| 103 | arg := fc.Args[argIdx] |
| 104 | argIdx++ |
| 105 | flushLit() |
| 106 | switch spec { |
| 107 | case 's': |
| 108 | // PG renders a NULL %s argument as the empty string. |
| 109 | segments = append(segments, coalesceNode(castToVarchar(arg), strConstNode(""))) |
| 110 | case 'I': |
| 111 | segments = append(segments, funcCallNode("quote_ident", arg)) |
| 112 | case 'L': |
| 113 | // quote_nullable matches PG %L exactly: NULL -> unquoted keyword NULL. |
| 114 | segments = append(segments, funcCallNode("quote_nullable", arg)) |
| 115 | } |
| 116 | default: |
| 117 | return rewriteToError("format(): unsupported format specifier %" + string(spec) + |
| 118 | " (only %s, %I, %L and %% are supported)") |
| 119 | } |
| 120 | } |
| 121 | flushLit() |
| 122 | if len(segments) == 0 { |
no test coverage detected