func deparseConst(node *C.Const, ctx *deparseCtx) {
	var (
		typeoutput   C.Oid
		typeIsVarLen C.bool
		actualValue  *C.char
	)
	buf := ctx.buf
	if bool(node.constisnull) {
		buf.WriteString("NULL")
		return
	}
	C.getTypeOutputInfo(node.consttype, &typeoutput, &typeIsVarLen)
	switch node.consttype {
	case C.INT2OID:
		fallthrough
	case C.INT4OID:
		fallthrough
	case C.INT8OID:
		fallthrough
	case C.OIDOID:
		fallthrough
	case C.FLOAT4OID:
		fallthrough
	case C.FLOAT8OID:
		fallthrough
	case C.NUMERICOID:
		actualValue = C.OidOutputFunctionCall(typeoutput, node.constvalue)
		if C.strspn(actualValue, C.CString("0123456789+-eE.")) == C.strlen(actualValue) {
			buf.WriteString(C.GoString(actualValue))
		} else {
			buf.WriteString(fmt.Sprintf("'%s'", C.GoString(actualValue)))
		}
	case C.BITOID:
	case C.VARBITOID:
	case C.BYTEAOID:
		errLogger.Println("binary type deparse not supported")
	case C.INT2ARRAYOID:
		fallthrough
	case C.INT4ARRAYOID:
		fallthrough
	case C.INT8ARRAYOID:
		fallthrough
	case C.OIDARRAYOID:
		fallthrough
	case C.FLOAT4ARRAYOID:
		fallthrough
	case C.FLOAT8ARRAYOID:
		fallthrough
	case C.NUMERICARRAYOID:
		actualValue = C.OidOutputFunctionCall(typeoutput, node.constvalue)
		value := C.GoString(actualValue)
		value = strings.TrimLeft(value, "{")
		value = strings.TrimRight(value, "}")
		buf.WriteString("[")
		buf.WriteString(value)
		buf.WriteString("]")
	case C.TEXTARRAYOID:
		fallthrough
	case C.VARCHARARRAYOID:
		actualValue = C.OidOutputFunctionCall(typeoutput, node.constvalue)
		value := C.GoString(actualValue)
		value = strings.TrimLeft(value, "{")
		value = strings.TrimRight(value, "}")
		separated := strings.Split(value, ",")
		buf.WriteString("[")
		for i, item := range separated {
			item = fmt.Sprintf("'%s'", item)
			if i != 0 {
				buf.WriteString(",")
			}
			buf.WriteString(item)
		}
		buf.WriteString("]")
	default:
		actualValue = C.OidOutputFunctionCall(typeoutput, node.constvalue)
		buf.WriteString(fmt.Sprintf("'%s'", C.GoString(actualValue)))
	}
}
deparseConst for all different constant types
see get_const_expr() in ruleutils.c for ideas
package main
import "C"
import (
	"fmt"
	"strings"
	"unsafe"
)
type deparseCtx struct {
	root *C.PlannerInfo
	scanrel *C.RelOptInfo
	buf *strings.Builder
}
var mapPGfuncToCH = map[string]string{
	"lower":     "lower",
	"upper":     "upper",
	"replace":   "replaceAll",
	"abs":       "abs",
	"round":     "round",
	"substr":    "substring",
	"sum":       "sum",
	"avg":       "avg",
	"max":       "max",
	"min":       "min",
	"count":     "count",
	"date_part": "__placeholder__",
	"timestamp": "toDateTime",
}
var tsDurations = []string{"'month'", "'year'", "'minute'", "'second'", "'hour'"}
func appendConditions(exprs *C.List, ctx *deparseCtx) {
	var expr *C.Expr
	var first = true
	buf := ctx.buf
	for cell := C.list_head(exprs); cell != nil; cell = C.wrapper_lnext(exprs, cell) {
		item := (unsafe.Pointer(C.wrapper_lfirst(cell)))
		if C.wrapper_nodeTag((*C.Node)(item)) == C.T_RestrictInfo {
			ri := (*C.RestrictInfo)(item)
			expr = (*C.Expr)(unsafe.Pointer(ri.clause))
		} else {
			expr = (*C.Expr)(item)
		}
		if !first {
			buf.WriteString(" AND ")
		}
		buf.WriteString("(")
		deparseExpr(expr, ctx)
		buf.WriteString(")")
		first = false
	}
}
func deparseExpr(expr *C.Expr, ctx *deparseCtx) {
	node := (*C.Node)(unsafe.Pointer(expr))
	if node == nil {
		return
	}
	switch C.wrapper_nodeTag(node) {
	case C.T_Const:
		deparseConst((*C.Const)(unsafe.Pointer(node)), ctx)
	case C.T_Var:
		deparseVar((*C.Var)(unsafe.Pointer(node)), ctx)
	case C.T_OpExpr:
		deparseOpExpr((*C.OpExpr)(unsafe.Pointer(node)), ctx)
	case C.T_FuncExpr:
		deparseFuncExpr((*C.FuncExpr)(unsafe.Pointer(node)), ctx)
	case C.T_ScalarArrayOpExpr:
		deparseScalarArrayOpExpr((*C.ScalarArrayOpExpr)(unsafe.Pointer(node)), ctx)
	case C.T_RelabelType:
		deparseRelabelType((*C.RelabelType)(unsafe.Pointer(node)), ctx)
	case C.T_BoolExpr:
		deparseBoolExpr((*C.BoolExpr)(unsafe.Pointer(node)), ctx)
	case C.T_NullTest:
		deparseNullTest((*C.NullTest)(unsafe.Pointer(node)), ctx)
	case C.T_ArrayExpr:
		deparseArrayExpr((*C.ArrayExpr)(unsafe.Pointer(node)), ctx)
	case C.T_CaseExpr:
		deparseCaseExpr((*C.CaseExpr)(unsafe.Pointer(node)), ctx)
	case C.T_CoalesceExpr:
		deparseCoalesceExpr((*C.CoalesceExpr)(unsafe.Pointer(node)), ctx)
	case C.T_NullIfExpr:
		deparseNullIfExpr((*C.NullIfExpr)(unsafe.Pointer(node)), ctx)
	case C.T_Aggref:
		deparseAggref((*C.Aggref)(unsafe.Pointer(node)), ctx)
	default:
		errLogger.Printf("unsupported expression type %v to deparse", int(C.wrapper_nodeTag(node)))
	}
}
func deparseOpExpr(node *C.OpExpr, ctx *deparseCtx) {
	var (
		tuple   C.HeapTuple
		form    C.Form_pg_operator
		oprkind C.char
		opname  string
		arg     *C.ListCell
	)
	buf := ctx.buf
	tuple = C.wrapper_SearchSysCache1(C.OPEROID, C.wrapper_ObjectIdGetDatum(node.opno))
	if bool(!C.wrapper_HeapTupleIsValid(tuple)) {
		errLogger.Printf("cache lookup failed for operator %v", node.opno)
	}
	form = (C.Form_pg_operator)(unsafe.Pointer(C.wrapper_GETSTRUCT(tuple)))
	oprkind = form.oprkind
	opname = C.GoString(&form.oprname.data[0])
	if !((oprkind == C.char('r') && C.list_length(node.args) == C.int(1)) ||
		(oprkind == C.char('l') && C.list_length(node.args) == C.int(1)) ||
		(oprkind == C.char('b') && C.list_length(node.args) == C.int(2))) {
		errLogger.Printf("incorrect args length to operator %v", node.opno)
	}
	buf.WriteString("(")
	if oprkind == C.char('r') || oprkind == C.char('b') {
		arg = C.list_head(node.args)
		deparseExpr((*C.Expr)(unsafe.Pointer(C.wrapper_lfirst(arg))), ctx)
		buf.WriteString(" ")
	}
	opname = C.GoString(&form.oprname.data[0])
	if opname == "~~" {
		buf.WriteString("LIKE")
	} else if opname == "!~~" {
		buf.WriteString("NOT LIKE")
	} else if opname == "~~*" || opname == "!~~*" || opname == "~" || opname == "!~" || opname == "~*" || opname == "!~*" {
		errLogger.Printf("operator is not supported")
	} else {
		buf.WriteString(opname)
	}
	if oprkind == C.char('l') || oprkind == C.char('b') {
		arg = C.list_tail(node.args)
		buf.WriteString(" ")
		deparseExpr((*C.Expr)(unsafe.Pointer(C.wrapper_lfirst(arg))), ctx)
	}
	buf.WriteString(")")
	C.ReleaseSysCache(tuple)
}
func deparseVar(node *C.Var, ctx *deparseCtx) {
	var (
		column      C.StringInfoData
		relIds      C.Relids
		varlevelsup C.Index
	)
	C.initStringInfo(&column)
	relIds = ctx.scanrel.relids
	varlevelsup = node.varlevelsup
	buf := ctx.buf
	if bool(C.bms_is_member(C.int(node.varno), relIds)) && C.uint(varlevelsup) == C.uint(0) {
		C.deparse_column_ref(&column, C.int(node.varno), C.int(node.varattno), ctx.root)
	} else {
		errLogger.Printf("no params support yet")
	}
	buf.WriteString(strings.TrimSpace((C.GoString(column.data))))
}