MCPcopy Create free account
hub / github.com/PostHog/duckgres / transformExpression

Method transformExpression

transpiler/transform/operators.go:369–603  ·  view source on GitHub ↗

transformExpression recursively transforms an expression, replacing regex operators with function calls. Returns the new node if transformed, nil otherwise.

(node *pg_query.Node)

Source from the content-addressed store, hash-verified

367// transformExpression recursively transforms an expression, replacing regex operators
368// with function calls. Returns the new node if transformed, nil otherwise.
369func (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

Callers 10

transformSelectStmtMethod · 0.95
transformSortClauseMethod · 0.95
transformWindowDefMethod · 0.95
transformUpdateStmtMethod · 0.95
transformDeleteStmtMethod · 0.95
transformFromItemMethod · 0.95
createJSONConcatExprMethod · 0.95

Calls 11

getOperatorNameMethod · 0.95
createJSONConcatExprMethod · 0.95
createRegexFuncCallMethod · 0.95
transformSortClauseMethod · 0.95
transformWindowDefMethod · 0.95
transformSelectStmtMethod · 0.95
looksJSONFunction · 0.85
pgPathArrayToJSONPathFunction · 0.85
stringConstNodeFunction · 0.85

Tested by

no test coverage detected