transformExpression recursively transforms an expression, replacing regex operators with function calls. Returns the new node if transformed, nil otherwise.
(node *pg_query.Node)
| 367 | // transformExpression recursively transforms an expression, replacing regex operators |
| 368 | // with function calls. Returns the new node if transformed, nil otherwise. |
| 369 | func (t *OperatorTransform) transformExpression(node *pg_query.Node) *pg_query.Node { |
| 370 | if node == nil { |
| 371 | return nil |
| 372 | } |
| 373 | |
| 374 | // Check if this is an operator A_Expr that needs transformation |
| 375 | if aexpr := node.GetAExpr(); aexpr != nil { |
| 376 | opName := t.getOperatorName(aexpr) |
| 377 | |
| 378 | switch opName { |
| 379 | // JSON operators - convert to function calls to avoid DuckDB precedence issues |
| 380 | // DuckDB parses "a AND b -> 'key'" as "(a AND b) -> 'key'" instead of "a AND (b -> 'key')" |
| 381 | case "->": |
| 382 | return t.createJsonExtractFuncCall(aexpr.Lexpr, aexpr.Rexpr, false) |
| 383 | case "->>": |
| 384 | return t.createJsonExtractFuncCall(aexpr.Lexpr, aexpr.Rexpr, true) |
| 385 | // jsonb || jsonb is concatenation in Postgres (object merge / array |
| 386 | // concat), but DuckDB treats || as string concatenation, silently |
| 387 | // producing invalid JSON. Rewrite to a json_type()-dispatching CASE |
| 388 | // that reproduces Postgres semantics only when an operand is clearly |
| 389 | // JSON; otherwise leave || alone (string/array concat is the safe |
| 390 | // default). |
| 391 | case "||": |
| 392 | if looksJSON(aexpr.Lexpr) || looksJSON(aexpr.Rexpr) { |
| 393 | return t.createJSONConcatExpr(aexpr.Lexpr, aexpr.Rexpr) |
| 394 | } |
| 395 | // jsonb @> jsonb is containment in Postgres; DuckDB has no @>(JSON,JSON) but does |
| 396 | // have json_contains(). Only rewrite when an operand is clearly JSON — array @> array |
| 397 | // is native in DuckDB and must be left alone. |
| 398 | case "@>": |
| 399 | if looksJSON(aexpr.Lexpr) || looksJSON(aexpr.Rexpr) { |
| 400 | return t.createJSONContainsFuncCall(aexpr.Lexpr, aexpr.Rexpr) |
| 401 | } |
| 402 | // json #>> '{a,b}' extracts the value at a text[] path as text. DuckDB lacks #>> but |
| 403 | // json_extract_string(j, '$."a"."b"') is equivalent. Only literal path arrays can be |
| 404 | // converted at transpile time; a non-literal path is left as-is. |
| 405 | case "#>>": |
| 406 | if jsonPath := pgPathArrayToJSONPath(aexpr.Rexpr); jsonPath != "" { |
| 407 | return t.createJsonExtractFuncCall(aexpr.Lexpr, stringConstNode(jsonPath), true) |
| 408 | } |
| 409 | // Regex operators — only match binary ~ (both operands present). |
| 410 | // Unary ~ (bitwise NOT, e.g. ~id) has Lexpr=nil and must be left as-is; |
| 411 | // DuckDB supports ~ as bitwise NOT natively. Passing nil into |
| 412 | // createRegexFuncCall would create a nil AST node that crashes pg_query.Deparse. |
| 413 | case "~": |
| 414 | if aexpr.Lexpr != nil { |
| 415 | return t.createRegexFuncCall(aexpr.Lexpr, aexpr.Rexpr, false, false) |
| 416 | } |
| 417 | case "~*": |
| 418 | return t.createRegexFuncCall(aexpr.Lexpr, aexpr.Rexpr, true, false) |
| 419 | case "!~": |
| 420 | return t.createRegexFuncCall(aexpr.Lexpr, aexpr.Rexpr, false, true) |
| 421 | case "!~*": |
| 422 | return t.createRegexFuncCall(aexpr.Lexpr, aexpr.Rexpr, true, true) |
| 423 | } |
| 424 | |
| 425 | // Recursively transform operands for other operators |
| 426 | leftChanged := false |
no test coverage detected