Files
upstream-mirror 0f4df23290
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
2026-09-16 02:17:34 +05:00

1245 lines
32 KiB
Go

package duckdb
import (
"math/big"
"strconv"
"strings"
dw "github.com/sqlc-dev/darkwing/ast"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
)
type cc struct{}
// loc returns a node's start offset for the Location fields of the sqlc AST.
// darkwing marks synthesized nodes with a negative span.
func (c *cc) loc(n dw.Node) int {
if n == nil {
return 0
}
if pos := n.Pos(); pos >= 0 {
return pos
}
return 0
}
func (c *cc) convert(node dw.Stmt) ast.Node {
switch n := node.(type) {
case *dw.SelectStatement:
return c.convertSelectStatement(n)
case *dw.InsertStatement:
return c.convertInsertStatement(n)
case *dw.UpdateStatement:
return c.convertUpdateStatement(n)
case *dw.DeleteStatement:
return c.convertDeleteStatement(n)
case *dw.TruncateStatement:
return c.convertTruncateStatement(n)
case *dw.CreateStatement:
return c.convertCreateStatement(n)
case *dw.DropStatement:
return c.convertDropStatement(n)
case *dw.AlterStatement:
return c.convertAlterStatement(n)
default:
return todo(n)
}
}
func (c *cc) convertSelectStatement(n *dw.SelectStatement) ast.Node {
if n == nil {
return &ast.TODO{}
}
return c.convertQueryNode(n.Node)
}
func (c *cc) convertQueryNode(q dw.QueryNode) ast.Node {
switch n := q.(type) {
case *dw.SelectNode:
return c.convertSelectNode(n)
case *dw.SetOperationNode:
return c.convertSetOperationNode(n)
case *dw.RecursiveCTENode:
return c.convertRecursiveCTENode(n)
default:
return todo(q)
}
}
// applyModifiers folds a query node's result modifiers — ORDER BY, LIMIT,
// DISTINCT — into the converted statement.
func (c *cc) applyModifiers(stmt *ast.SelectStmt, mods []dw.ResultModifier) {
for _, mod := range mods {
switch m := mod.(type) {
case *dw.OrderModifier:
stmt.SortClause = c.convertOrderBys(m.Orders)
case *dw.LimitModifier:
// LIMIT n% has no row-count equivalent.
if m.LimitType == dw.LimitPercentage {
continue
}
if m.Limit != nil {
stmt.LimitCount = c.convertExpr(m.Limit)
}
if m.Offset != nil {
stmt.LimitOffset = c.convertExpr(m.Offset)
}
case *dw.DistinctModifier:
stmt.DistinctClause = &ast.List{}
for _, target := range m.DistinctOnTargets {
stmt.DistinctClause.Items = append(stmt.DistinctClause.Items, c.convertExpr(target))
}
}
}
}
func (c *cc) convertWithClause(ctes dw.CTEMap) *ast.WithClause {
if len(ctes.Entries) == 0 {
return nil
}
with := &ast.WithClause{Ctes: &ast.List{}}
for _, entry := range ctes.Entries {
cte := entry.CTE
name := identifier(entry.Name)
item := &ast.CommonTableExpr{
Ctename: &name,
Ctequery: c.convertQueryNode(cte.Query),
Location: c.loc(cte),
}
// A recursive CTE parses into its own query node.
if _, ok := cte.Query.(*dw.RecursiveCTENode); ok {
with.Recursive = true
item.Cterecursive = true
}
if len(cte.Aliases) > 0 {
item.Aliascolnames = &ast.List{}
for _, alias := range cte.Aliases {
item.Aliascolnames.Items = append(item.Aliascolnames.Items, NewIdentifier(alias))
}
}
with.Ctes.Items = append(with.Ctes.Items, item)
}
return with
}
// isBareStar reports a plain "*" projection with no modifiers, the select
// list darkwing synthesizes when desugaring VALUES into a select.
func isBareStar(exprs []dw.Expr) bool {
if len(exprs) != 1 {
return false
}
star, ok := exprs[0].(*dw.StarExpression)
return ok && star.RelationName == "" && !star.Columns && star.Expr == nil &&
len(star.ExcludeList) == 0 && len(star.QualifiedExcludeList) == 0 &&
len(star.ReplaceList) == 0 && len(star.RenameList) == 0
}
func (c *cc) convertValuesLists(values [][]dw.Expr) *ast.List {
lists := &ast.List{}
for _, row := range values {
rowList := &ast.List{}
for _, val := range row {
rowList.Items = append(rowList.Items, c.convertExpr(val))
}
lists.Items = append(lists.Items, rowList)
}
return lists
}
func (c *cc) convertSelectNode(n *dw.SelectNode) ast.Node {
stmt := &ast.SelectStmt{}
stmt.WithClause = c.convertWithClause(n.CTEs)
// VALUES (...), (...) parses as SELECT * FROM an expression list.
if list, ok := n.FromTable.(*dw.ExpressionListRef); ok && isBareStar(n.SelectList) {
stmt.ValuesLists = c.convertValuesLists(list.Values)
c.applyModifiers(stmt, n.Modifiers)
return stmt
}
if len(n.SelectList) > 0 {
stmt.TargetList = &ast.List{}
for _, expr := range n.SelectList {
stmt.TargetList.Items = append(stmt.TargetList.Items, c.convertResTarget(expr))
}
}
if from := c.convertTableRef(n.FromTable); from != nil {
stmt.FromClause = &ast.List{Items: []ast.Node{from}}
}
if n.Where != nil {
stmt.WhereClause = c.convertExpr(n.Where)
}
if len(n.GroupExpressions) > 0 {
stmt.GroupClause = &ast.List{}
for _, expr := range n.GroupExpressions {
stmt.GroupClause.Items = append(stmt.GroupClause.Items, c.convertExpr(expr))
}
}
if n.Having != nil {
stmt.HavingClause = c.convertExpr(n.Having)
}
c.applyModifiers(stmt, n.Modifiers)
return stmt
}
func (c *cc) convertSetOperationNode(n *dw.SetOperationNode) ast.Node {
var op ast.SetOperation
switch n.SetOpType {
case dw.SetOpUnion, dw.SetOpUnionByName:
op = ast.Union
case dw.SetOpExcept:
op = ast.Except
case dw.SetOpIntersect:
op = ast.Intersect
default:
return todo(n)
}
// DuckDB v2 set operations are n-ary; sqlc's are binary, so a chain
// folds left-associatively.
var stmt *ast.SelectStmt
for _, input := range n.Inputs {
arg, ok := c.convertQueryNode(input).(*ast.SelectStmt)
if !ok {
return todo(n)
}
if stmt == nil {
stmt = arg
continue
}
stmt = &ast.SelectStmt{
Op: op,
All: n.SetOpAll,
Larg: stmt,
Rarg: arg,
}
}
if stmt == nil {
return todo(n)
}
stmt.WithClause = c.convertWithClause(n.CTEs)
c.applyModifiers(stmt, n.Modifiers)
return stmt
}
func (c *cc) convertRecursiveCTENode(n *dw.RecursiveCTENode) ast.Node {
larg, ok := c.convertQueryNode(n.Left).(*ast.SelectStmt)
if !ok {
return todo(n)
}
rarg, ok := c.convertQueryNode(n.Right).(*ast.SelectStmt)
if !ok {
return todo(n)
}
stmt := &ast.SelectStmt{
Op: ast.Union,
All: n.UnionAll,
Larg: larg,
Rarg: rarg,
}
c.applyModifiers(stmt, n.Modifiers)
return stmt
}
func (c *cc) convertResTarget(expr dw.Expr) *ast.ResTarget {
res := &ast.ResTarget{
Val: c.convertExpr(expr),
Location: c.loc(expr),
}
if alias := expr.GetAlias(); alias != "" {
name := identifier(alias)
res.Name = &name
}
return res
}
func (c *cc) convertOrderBys(orders []dw.OrderByNode) *ast.List {
list := &ast.List{}
for _, order := range orders {
sortBy := &ast.SortBy{
Node: c.convertExpr(order.Expression),
Location: c.loc(order.Expression),
}
switch order.Type {
case dw.OrderAscending:
sortBy.SortbyDir = ast.SortByDirAsc
case dw.OrderDescending:
sortBy.SortbyDir = ast.SortByDirDesc
default:
sortBy.SortbyDir = ast.SortByDirDefault
}
switch order.NullOrder {
case dw.NullsFirst:
sortBy.SortbyNulls = ast.SortByNullsFirst
case dw.NullsLast:
sortBy.SortbyNulls = ast.SortByNullsLast
default:
sortBy.SortbyNulls = ast.SortByNullsDefault
}
list.Items = append(list.Items, sortBy)
}
return list
}
func (c *cc) convertAlias(name string, columns []string) *ast.Alias {
if name == "" && len(columns) == 0 {
return nil
}
aliasName := identifier(name)
alias := &ast.Alias{Aliasname: &aliasName}
if len(columns) > 0 {
alias.Colnames = &ast.List{}
for _, col := range columns {
alias.Colnames.Items = append(alias.Colnames.Items, NewIdentifier(col))
}
}
return alias
}
func (c *cc) convertRangeVar(catalog, schema, table, alias string, loc int) *ast.RangeVar {
name := parseTableName(catalog, schema, table)
rv := &ast.RangeVar{
Relname: &name.Name,
Location: loc,
}
if name.Schema != "" {
rv.Schemaname = &name.Schema
}
if name.Catalog != "" {
rv.Catalogname = &name.Catalog
}
if alias != "" {
rv.Alias = c.convertAlias(alias, nil)
}
return rv
}
func (c *cc) convertTableRef(ref dw.TableRef) ast.Node {
switch t := ref.(type) {
case nil, *dw.EmptyTableRef:
return nil
case *dw.BaseTableRef:
rv := c.convertRangeVar(t.CatalogName, t.SchemaName, t.TableName, "", c.loc(t))
if alias := c.convertAlias(t.Alias, t.ColumnNameAlias); alias != nil {
rv.Alias = alias
}
return rv
case *dw.JoinRef:
return c.convertJoinRef(t)
case *dw.SubqueryRef:
return &ast.RangeSubselect{
Subquery: c.convertSelectStatement(t.Subquery),
Alias: c.convertAlias(t.Alias, t.ColumnNameAlias),
}
case *dw.TableFunctionRef:
return c.convertTableFunctionRef(t)
case *dw.ExpressionListRef:
// A VALUES list used as a relation becomes a values subquery.
sub := &ast.RangeSubselect{
Subquery: &ast.SelectStmt{ValuesLists: c.convertValuesLists(t.Values)},
Alias: c.convertAlias(t.Alias, t.ExpectedNames),
}
return sub
default:
return todo(ref)
}
}
func (c *cc) convertJoinRef(t *dw.JoinRef) ast.Node {
join := &ast.JoinExpr{
Larg: c.convertTableRef(t.Left),
Rarg: c.convertTableRef(t.Right),
}
switch t.JoinType {
case dw.JoinLeft:
join.Jointype = ast.JoinTypeLeft
case dw.JoinRight:
join.Jointype = ast.JoinTypeRight
case dw.JoinFull:
join.Jointype = ast.JoinTypeFull
default:
join.Jointype = ast.JoinTypeInner
}
if t.RefType == dw.JoinRefNatural {
join.IsNatural = true
}
if t.Condition != nil {
join.Quals = c.convertExpr(t.Condition)
}
if len(t.UsingColumns) > 0 {
join.UsingClause = &ast.List{}
for _, col := range t.UsingColumns {
join.UsingClause.Items = append(join.UsingClause.Items, NewIdentifier(col))
}
}
return join
}
func (c *cc) convertTableFunctionRef(t *dw.TableFunctionRef) ast.Node {
call, ok := c.convertExpr(t.Function).(*ast.FuncCall)
if !ok {
return todo(t)
}
return &ast.RangeFunction{
Functions: &ast.List{Items: []ast.Node{call}},
Alias: c.convertAlias(t.Alias, t.ColumnNameAlias),
Ordinality: t.WithOrdinality,
}
}
func (c *cc) convertExpr(expr dw.Expr) ast.Node {
if expr == nil {
return nil
}
switch e := expr.(type) {
case *dw.ColumnRefExpression:
return c.convertColumnRef(e)
case *dw.ConstantExpression:
return c.convertConstant(e)
case *dw.ParameterExpression:
return c.convertParameter(e)
case *dw.FunctionExpression:
return c.convertFunction(e)
case *dw.OperatorExpression:
return c.convertOperator(e)
case *dw.ComparisonExpression:
return c.convertComparison(e)
case *dw.ConjunctionExpression:
return c.convertConjunction(e)
case *dw.CastExpression:
return c.convertCast(e)
case *dw.CaseExpression:
return c.convertCase(e)
case *dw.BetweenExpression:
return &ast.BetweenExpr{
Expr: c.convertExpr(e.Input),
Left: c.convertExpr(e.Lower),
Right: c.convertExpr(e.Upper),
Location: c.loc(e),
}
case *dw.SubqueryExpression:
return c.convertSubquery(e)
case *dw.StarExpression:
return c.convertStar(e)
case *dw.CollateExpression:
return &ast.CollateExpr{
Arg: c.convertExpr(e.Child),
Location: c.loc(e),
}
case *dw.WindowExpression:
return c.convertWindow(e)
default:
return todo(expr)
}
}
func (c *cc) convertColumnRef(e *dw.ColumnRefExpression) *ast.ColumnRef {
fields := &ast.List{}
for _, name := range e.ColumnNames {
fields.Items = append(fields.Items, NewIdentifier(name))
}
return &ast.ColumnRef{
Fields: fields,
Location: c.loc(e),
}
}
// decimalString renders a decimal's unscaled digits with the point
// re-inserted, e.g. ("15", 1) => "1.5".
func decimalString(digits string, scale int) string {
neg := strings.HasPrefix(digits, "-")
if neg {
digits = digits[1:]
}
if scale > 0 {
for len(digits) <= scale {
digits = "0" + digits
}
digits = digits[:len(digits)-scale] + "." + digits[len(digits)-scale:]
}
if neg {
digits = "-" + digits
}
return digits
}
func hugeintString(h dw.Hugeint) string {
v := new(big.Int).SetInt64(h.Upper)
v.Lsh(v, 64)
return v.Or(v, new(big.Int).SetUint64(h.Lower)).String()
}
func (c *cc) convertConstant(e *dw.ConstantExpression) ast.Node {
v := e.Value
konst := &ast.A_Const{Location: c.loc(e)}
switch {
case v.IsNull || v.Kind == dw.ValueNull:
konst.Val = &ast.Null{}
case v.Kind == dw.ValueBool:
konst.Val = &ast.Boolean{Boolval: v.Bool}
case v.Kind == dw.ValueInt64 && v.Type.ID == "DECIMAL":
// A decimal literal carries its unscaled value.
konst.Val = &ast.Float{Str: decimalString(strconv.FormatInt(v.Int64, 10), v.Type.Scale)}
case v.Kind == dw.ValueInt64:
konst.Val = &ast.Integer{Ival: v.Int64}
case v.Kind == dw.ValueHugeint && v.Type.ID == "DECIMAL":
konst.Val = &ast.Float{Str: decimalString(hugeintString(v.Hugeint), v.Type.Scale)}
case v.Kind == dw.ValueHugeint:
konst.Val = &ast.Float{Str: hugeintString(v.Hugeint)}
case v.Kind == dw.ValueDouble:
konst.Val = &ast.Float{Str: strconv.FormatFloat(v.Float64, 'g', -1, 64)}
case v.Kind == dw.ValueString:
konst.Val = &ast.String{Str: v.Str}
default:
return todo(e)
}
return konst
}
// convertParameter converts a prepared-statement parameter. darkwing has
// already numbered every spelling: explicit for $3 and ?3, in order of
// appearance for ?, and in order of first use for $name.
func (c *cc) convertParameter(e *dw.ParameterExpression) ast.Node {
return &ast.ParamRef{
Number: e.Number,
Dollar: e.Kind == dw.ParameterNumbered || e.Kind == dw.ParameterNamed,
Location: c.loc(e),
}
}
func (c *cc) convertFunction(e *dw.FunctionExpression) ast.Node {
name := identifier(e.FunctionName)
// Binary and prefix operators parse as operator-named functions.
if e.IsOperator {
switch len(e.Arguments) {
case 1:
return &ast.A_Expr{
Kind: ast.A_Expr_Kind_OP,
Name: &ast.List{Items: []ast.Node{&ast.String{Str: e.FunctionName}}},
Rexpr: c.convertExpr(e.Arguments[0].Expr),
Location: c.loc(e),
}
case 2:
return &ast.A_Expr{
Kind: ast.A_Expr_Kind_OP,
Name: &ast.List{Items: []ast.Node{&ast.String{Str: e.FunctionName}}},
Lexpr: c.convertExpr(e.Arguments[0].Expr),
Rexpr: c.convertExpr(e.Arguments[1].Expr),
Location: c.loc(e),
}
default:
return todo(e)
}
}
fc := &ast.FuncCall{
Funcname: &ast.List{},
Location: c.loc(e),
AggDistinct: e.Distinct,
}
if schema := schemaName(e.Schema); schema != "" {
fc.Funcname.Items = append(fc.Funcname.Items, &ast.String{Str: schema})
}
// COUNT(*) parses as the argument-less count_star.
if name == "count_star" {
fc.Funcname.Items = append(fc.Funcname.Items, &ast.String{Str: "count"})
fc.AggStar = true
return fc
}
fc.Funcname.Items = append(fc.Funcname.Items, &ast.String{Str: name})
for _, arg := range e.Arguments {
if fc.Args == nil {
fc.Args = &ast.List{}
}
fc.Args.Items = append(fc.Args.Items, c.convertExpr(arg.Expr))
}
if e.Filter != nil {
fc.AggFilter = c.convertExpr(e.Filter)
}
if len(e.OrderBys) > 0 {
fc.AggOrder = c.convertOrderBys(e.OrderBys)
}
return fc
}
func (c *cc) convertOperator(e *dw.OperatorExpression) ast.Node {
switch e.Type {
case dw.OperatorNot:
// NOT IN parses as NOT wrapping an IN.
if len(e.Operands) == 1 {
if in, ok := e.Operands[0].(*dw.OperatorExpression); ok && in.Type == dw.CompareIn {
node := c.convertIn(in)
node.Not = true
return node
}
}
return c.convertBoolExpr(ast.BoolExprTypeNot, e)
case dw.OperatorIsNull, dw.OperatorIsNotNull:
if len(e.Operands) != 1 {
return todo(e)
}
test := ast.NullTestTypeIsNull
if e.Type == dw.OperatorIsNotNull {
test = ast.NullTestTypeIsNotNull
}
return &ast.NullTest{
Arg: c.convertExpr(e.Operands[0]),
Nulltesttype: test,
Location: c.loc(e),
}
case dw.CompareIn, dw.CompareNotIn:
node := c.convertIn(e)
node.Not = e.Type == dw.CompareNotIn
return node
case dw.OperatorCoalesce:
coalesce := &ast.CoalesceExpr{
Args: &ast.List{},
Location: c.loc(e),
}
for _, operand := range e.Operands {
coalesce.Args.Items = append(coalesce.Args.Items, c.convertExpr(operand))
}
return coalesce
case dw.ArrayConstructor:
arr := &ast.A_ArrayExpr{
Elements: &ast.List{},
Location: c.loc(e),
}
for _, operand := range e.Operands {
arr.Elements.Items = append(arr.Elements.Items, c.convertExpr(operand))
}
return arr
default:
return todo(e)
}
}
func (c *cc) convertIn(e *dw.OperatorExpression) *ast.In {
in := &ast.In{Location: c.loc(e)}
if len(e.Operands) > 0 {
in.Expr = c.convertExpr(e.Operands[0])
for _, item := range e.Operands[1:] {
in.List = append(in.List, c.convertExpr(item))
}
}
return in
}
func (c *cc) convertBoolExpr(op ast.BoolExprType, e *dw.OperatorExpression) ast.Node {
expr := &ast.BoolExpr{
Boolop: op,
Args: &ast.List{},
Location: c.loc(e),
}
for _, operand := range e.Operands {
expr.Args.Items = append(expr.Args.Items, c.convertExpr(operand))
}
return expr
}
func comparisonOperator(t dw.ExpressionType) (string, bool) {
switch t {
case dw.CompareEqual:
return "=", true
case dw.CompareNotEqual:
return "<>", true
case dw.CompareLessThan:
return "<", true
case dw.CompareGreaterThan:
return ">", true
case dw.CompareLessThanOrEqual:
return "<=", true
case dw.CompareGreaterThanOrEqual:
return ">=", true
}
return "", false
}
func (c *cc) convertComparison(e *dw.ComparisonExpression) ast.Node {
kind := ast.A_Expr_Kind_OP
op, ok := comparisonOperator(e.Type)
if !ok {
switch e.Type {
case dw.CompareDistinctFrom:
kind, op = ast.A_Expr_Kind_DISTINCT, "="
case dw.CompareNotDistinctFrom:
kind, op = ast.A_Expr_Kind_NOT_DISTINCT, "="
default:
return todo(e)
}
}
return &ast.A_Expr{
Kind: kind,
Name: &ast.List{Items: []ast.Node{&ast.String{Str: op}}},
Lexpr: c.convertExpr(e.Left),
Rexpr: c.convertExpr(e.Right),
Location: c.loc(e),
}
}
func (c *cc) convertConjunction(e *dw.ConjunctionExpression) ast.Node {
boolop := ast.BoolExprTypeAnd
if e.Type == dw.ConjunctionOr {
boolop = ast.BoolExprTypeOr
}
expr := &ast.BoolExpr{
Boolop: boolop,
Args: &ast.List{},
Location: c.loc(e),
}
for _, operand := range e.Operands {
expr.Args.Items = append(expr.Args.Items, c.convertExpr(operand))
}
return expr
}
func (c *cc) convertCast(e *dw.CastExpression) ast.Node {
cast := &ast.TypeCast{
Arg: c.convertExpr(e.Child),
Location: c.loc(e),
}
switch {
case e.CastType != nil:
typeName, arrayDims := c.convertTypeExpression(e.CastType)
if arrayDims > 0 {
typeName.ArrayBounds = &ast.List{}
for i := 0; i < arrayDims; i++ {
typeName.ArrayBounds.Items = append(typeName.ArrayBounds.Items, &ast.Integer{Ival: -1})
}
}
cast.TypeName = typeName
case e.ResolvedType != "":
cast.TypeName = &ast.TypeName{Name: identifier(e.ResolvedType)}
default:
return todo(e)
}
return cast
}
func (c *cc) convertCase(e *dw.CaseExpression) ast.Node {
caseExpr := &ast.CaseExpr{
Args: &ast.List{},
Location: c.loc(e),
}
for _, check := range e.CaseChecks {
caseExpr.Args.Items = append(caseExpr.Args.Items, &ast.CaseWhen{
Expr: c.convertExpr(check.When),
Result: c.convertExpr(check.Then),
})
}
if e.ElseExpr != nil {
caseExpr.Defresult = c.convertExpr(e.ElseExpr)
}
return caseExpr
}
func (c *cc) convertSubquery(e *dw.SubqueryExpression) ast.Node {
link := &ast.SubLink{
Subselect: c.convertSelectStatement(e.Subquery),
Location: c.loc(e),
}
switch e.SubqueryType {
case dw.SubqueryExists:
link.SubLinkType = ast.EXISTS_SUBLINK
case dw.SubqueryScalar:
link.SubLinkType = ast.EXPR_SUBLINK
case dw.SubqueryAny:
// x IN (SELECT ...) and quantified comparisons desugar to ANY.
link.SubLinkType = ast.ANY_SUBLINK
link.Testexpr = c.convertExpr(e.Child)
if op, ok := comparisonOperator(e.ComparisonType); ok {
link.OperName = &ast.List{Items: []ast.Node{&ast.String{Str: op}}}
}
default:
return todo(e)
}
return link
}
func (c *cc) convertStar(e *dw.StarExpression) ast.Node {
// COLUMNS(...) and EXCLUDE/REPLACE/RENAME modifiers have no equivalent.
if e.Columns || e.Expr != nil || len(e.ExcludeList) > 0 ||
len(e.QualifiedExcludeList) > 0 || len(e.ReplaceList) > 0 || len(e.RenameList) > 0 {
return todo(e)
}
fields := &ast.List{}
if e.RelationName != "" {
fields.Items = append(fields.Items, NewIdentifier(e.RelationName))
}
fields.Items = append(fields.Items, &ast.A_Star{})
return &ast.ColumnRef{
Fields: fields,
Location: c.loc(e),
}
}
func (c *cc) convertWindow(e *dw.WindowExpression) ast.Node {
fc := &ast.FuncCall{
Funcname: &ast.List{Items: []ast.Node{&ast.String{Str: identifier(e.FunctionName)}}},
Location: c.loc(e),
AggDistinct: e.Distinct,
Over: &ast.WindowDef{Location: c.loc(e)},
}
for _, arg := range e.Arguments {
if fc.Args == nil {
fc.Args = &ast.List{}
}
fc.Args.Items = append(fc.Args.Items, c.convertExpr(arg.Expr))
}
if e.FilterExpr != nil {
fc.AggFilter = c.convertExpr(e.FilterExpr)
}
if len(e.Partitions) > 0 {
fc.Over.PartitionClause = &ast.List{}
for _, p := range e.Partitions {
fc.Over.PartitionClause.Items = append(fc.Over.PartitionClause.Items, c.convertExpr(p))
}
}
if len(e.Orders) > 0 {
fc.Over.OrderClause = c.convertOrderBys(e.Orders)
}
return fc
}
// convertTypeExpression maps an unbound DuckDB type to a sqlc type name and
// the number of list/array dimensions wrapped around it, which is what the
// legacy catalog reads. The whole type — its arguments, a struct's fields,
// a map's key and value, the nesting of a list of lists — goes along as
// its spelling, which is what the analysis core reads.
func (c *cc) convertTypeExpression(t *dw.TypeExpression) (*ast.TypeName, int) {
typeName, dims := c.elementTypeName(t)
typeName.Canonical = renderTypeExpression(t)
return typeName, dims
}
func (c *cc) elementTypeName(t *dw.TypeExpression) (*ast.TypeName, int) {
name := identifier(t.TypeName)
switch name {
case "list", "array":
// int[] is LIST(INTEGER); int[3] is ARRAY(INTEGER, 3).
if len(t.Args) > 0 {
if elem, ok := t.Args[0].(*dw.TypeExpression); ok {
typeName, dims := c.elementTypeName(elem)
return typeName, dims + 1
}
}
}
return &ast.TypeName{
Schema: schemaName(t.Schema),
Name: name,
}, 0
}
// renderTypeExpression spells a type as a call expression the core reads:
// a list is array applied to its element, a fixed-size array carries its
// size, a struct's or union's fields are labelled, and a constant argument
// is written as it was.
func renderTypeExpression(t *dw.TypeExpression) string {
name := identifier(t.TypeName)
if schema := schemaName(t.Schema); schema != "" {
name = schema + "." + name
}
if name == "list" {
name = "array"
}
if len(t.Args) == 0 {
return name
}
parts := make([]string, 0, len(t.Args))
for _, arg := range t.Args {
var part string
switch a := arg.(type) {
case *dw.TypeExpression:
part = renderTypeExpression(a)
if a.Alias != "" {
part = identifier(a.Alias) + ": " + part
}
case *dw.ConstantExpression:
switch {
case a.Value.Str != "":
part = "'" + strings.ReplaceAll(a.Value.Str, "'", "''") + "'"
default:
part = strconv.FormatInt(a.Value.Int64, 10)
}
default:
continue
}
parts = append(parts, part)
}
return name + "(" + strings.Join(parts, ", ") + ")"
}
func (c *cc) convertReturning(returning []dw.Expr) *ast.List {
if len(returning) == 0 {
return nil
}
list := &ast.List{}
for _, expr := range returning {
list.Items = append(list.Items, c.convertResTarget(expr))
}
return list
}
func (c *cc) convertInsertStatement(n *dw.InsertStatement) ast.Node {
stmt := &ast.InsertStmt{
Relation: c.convertRangeVar(n.Catalog, n.Schema, n.Table, n.TableAlias, c.loc(n)),
WithClause: c.convertWithClause(n.CTEs),
DefaultValues: n.DefaultValues,
}
if len(n.Columns) > 0 {
stmt.Cols = &ast.List{}
for _, col := range n.Columns {
name := identifier(col)
stmt.Cols.Items = append(stmt.Cols.Items, &ast.ResTarget{Name: &name})
}
}
if n.Query != nil {
stmt.SelectStmt = c.convertSelectStatement(n.Query)
}
if n.OnConflict != nil {
stmt.OnConflictClause = c.convertOnConflict(n.OnConflict)
}
stmt.ReturningList = c.convertReturning(n.Returning)
return stmt
}
func (c *cc) convertOnConflict(n *dw.OnConflictInfo) *ast.OnConflictClause {
clause := &ast.OnConflictClause{Location: c.loc(n)}
switch n.Action {
case dw.OnConflictUpdate:
clause.Action = ast.OnConflictActionUpdate
default:
clause.Action = ast.OnConflictActionNothing
}
if n.SetInfo != nil {
clause.TargetList = c.convertSetClause(n.SetInfo)
if n.SetInfo.Condition != nil {
clause.WhereClause = c.convertExpr(n.SetInfo.Condition)
}
}
return clause
}
func (c *cc) convertSetClause(set *dw.UpdateSetInfo) *ast.List {
list := &ast.List{}
for i, col := range set.Columns {
if len(col) == 0 || i >= len(set.Expressions) {
continue
}
name := identifier(col[len(col)-1])
list.Items = append(list.Items, &ast.ResTarget{
Name: &name,
Val: c.convertExpr(set.Expressions[i]),
})
}
return list
}
func (c *cc) convertUpdateStatement(n *dw.UpdateStatement) ast.Node {
target := c.convertTableRef(n.Table)
if target == nil {
return todo(n)
}
stmt := &ast.UpdateStmt{
Relations: &ast.List{Items: []ast.Node{target}},
TargetList: &ast.List{},
WithClause: c.convertWithClause(n.CTEs),
}
if n.SetInfo != nil {
stmt.TargetList = c.convertSetClause(n.SetInfo)
}
if from := c.convertTableRef(n.From); from != nil {
stmt.FromClause = &ast.List{Items: []ast.Node{from}}
}
if n.Where != nil {
stmt.WhereClause = c.convertExpr(n.Where)
}
stmt.ReturningList = c.convertReturning(n.Returning)
return stmt
}
func (c *cc) convertDeleteStatement(n *dw.DeleteStatement) ast.Node {
target := c.convertTableRef(n.Table)
if target == nil {
return todo(n)
}
stmt := &ast.DeleteStmt{
Relations: &ast.List{Items: []ast.Node{target}},
WithClause: c.convertWithClause(n.CTEs),
}
if len(n.Using) > 0 {
stmt.UsingClause = &ast.List{}
for _, using := range n.Using {
if item := c.convertTableRef(using); item != nil {
stmt.UsingClause.Items = append(stmt.UsingClause.Items, item)
}
}
}
if n.Where != nil {
stmt.WhereClause = c.convertExpr(n.Where)
}
stmt.ReturningList = c.convertReturning(n.Returning)
return stmt
}
func (c *cc) convertTruncateStatement(n *dw.TruncateStatement) ast.Node {
target := c.convertTableRef(n.Table)
if target == nil {
return todo(n)
}
return &ast.TruncateStmt{
Relations: &ast.List{Items: []ast.Node{target}},
}
}
func (c *cc) convertCreateStatement(n *dw.CreateStatement) ast.Node {
switch info := n.Info.(type) {
case *dw.CreateTableInfo:
return c.convertCreateTableInfo(info)
case *dw.CreateViewInfo:
return c.convertCreateViewInfo(info)
case *dw.CreateTypeInfo:
return c.convertCreateTypeInfo(info)
default:
return todo(n)
}
}
func (c *cc) convertCreateTableInfo(info *dw.CreateTableInfo) ast.Node {
name := parseTableName(info.Catalog, info.Schema, info.Name)
// CREATE TABLE ... AS records the table as a relation defined by its
// query, like a view.
if info.Query != nil {
into := &ast.IntoClause{
Rel: c.convertRangeVar(info.Catalog, info.Schema, info.Name, "", c.loc(info)),
}
if len(info.QueryAliases) > 0 {
into.ColNames = &ast.List{}
for _, alias := range info.QueryAliases {
into.ColNames.Items = append(into.ColNames.Items, NewIdentifier(alias))
}
}
return &ast.CreateTableAsStmt{
Query: c.convertSelectStatement(info.Query),
Into: into,
IfNotExists: info.OnConflict == dw.CreateIgnore,
}
}
stmt := &ast.CreateTableStmt{
Name: name,
IfNotExists: info.OnConflict == dw.CreateIgnore,
}
// Column names in table-level PRIMARY KEY constraints are NOT NULL.
primaryKey := map[string]bool{}
for _, constraint := range info.Constraints {
unique, ok := constraint.(*dw.UniqueConstraint)
if !ok || !unique.IsPrimaryKey {
continue
}
for _, col := range unique.Columns {
primaryKey[identifier(col)] = true
}
}
for i := range info.Columns {
stmt.Cols = append(stmt.Cols, c.convertColumnDef(&info.Columns[i], primaryKey))
}
return stmt
}
func (c *cc) convertColumnDef(col *dw.ColumnDef, tablePrimaryKey map[string]bool) *ast.ColumnDef {
name := ""
if len(col.Names) > 0 {
name = identifier(col.Names[len(col.Names)-1])
}
def := &ast.ColumnDef{
Colname: name,
Location: c.loc(col),
}
if col.Type != nil {
typeName, arrayDims := c.convertTypeExpression(col.Type)
def.TypeName = typeName
def.ArrayDims = arrayDims
def.IsArray = arrayDims > 0
} else {
// A generated column may omit its type; DuckDB infers it at bind
// time.
def.TypeName = &ast.TypeName{Name: "any"}
}
for _, constraint := range col.Constraints {
switch con := constraint.(type) {
case *dw.NotNullConstraint:
def.IsNotNull = !con.Null
case *dw.UniqueConstraint:
if con.IsPrimaryKey {
def.PrimaryKey = true
def.IsNotNull = true
}
}
}
if tablePrimaryKey[name] {
def.PrimaryKey = true
def.IsNotNull = true
}
return def
}
func (c *cc) convertCreateViewInfo(info *dw.CreateViewInfo) ast.Node {
stmt := &ast.ViewStmt{
View: c.convertRangeVar(info.Catalog, info.Schema, info.Name, "", c.loc(info)),
Query: c.convertSelectStatement(info.Query),
Replace: info.OnConflict == dw.CreateReplace,
}
if len(info.Aliases) > 0 {
stmt.Aliases = &ast.List{}
for _, alias := range info.Aliases {
stmt.Aliases.Items = append(stmt.Aliases.Items, NewIdentifier(alias))
}
}
return stmt
}
func (c *cc) convertCreateTypeInfo(info *dw.CreateTypeInfo) ast.Node {
if !info.IsEnum {
return todo(info)
}
stmt := &ast.CreateEnumStmt{
TypeName: &ast.TypeName{
Schema: schemaName(info.Schema),
Name: identifier(info.Name),
},
Vals: &ast.List{},
}
for _, val := range info.EnumValues {
stmt.Vals.Items = append(stmt.Vals.Items, &ast.String{Str: val})
}
return stmt
}
func (c *cc) convertDropStatement(n *dw.DropStatement) ast.Node {
switch n.DropType {
case dw.DropTable, dw.DropView:
stmt := &ast.DropTableStmt{IfExists: n.IfExists}
for _, name := range n.Names {
stmt.Tables = append(stmt.Tables, parseTableName(name.Catalog, name.Schema, name.Name))
}
return stmt
default:
return todo(n)
}
}
func (c *cc) convertAlterStatement(n *dw.AlterStatement) ast.Node {
if n.Entity != dw.AlterEntityTable {
return todo(n)
}
table := parseTableName(n.Name.Catalog, n.Name.Schema, n.Name.Name)
var stmts []ast.Node
alter := &ast.AlterTableStmt{
Table: table,
Cmds: &ast.List{},
MissingOk: n.IfExists,
}
for _, action := range n.Actions {
switch a := action.(type) {
case *dw.AddColumnInfo:
def := c.convertColumnDef(&a.Column, nil)
alter.Cmds.Items = append(alter.Cmds.Items, &ast.AlterTableCmd{
Name: &def.Colname,
Subtype: ast.AT_AddColumn,
Def: def,
MissingOk: a.IfNotExists,
})
case *dw.DropColumnInfo:
if len(a.Column) == 0 {
continue
}
name := identifier(a.Column[len(a.Column)-1])
alter.Cmds.Items = append(alter.Cmds.Items, &ast.AlterTableCmd{
Name: &name,
Subtype: ast.AT_DropColumn,
MissingOk: a.IfExists,
})
case *dw.AlterColumnTypeInfo:
if len(a.Column) == 0 || a.Type == nil {
continue
}
name := identifier(a.Column[len(a.Column)-1])
typeName, arrayDims := c.convertTypeExpression(a.Type)
alter.Cmds.Items = append(alter.Cmds.Items, &ast.AlterTableCmd{
Name: &name,
Subtype: ast.AT_AlterColumnType,
Def: &ast.ColumnDef{
Colname: name,
TypeName: typeName,
ArrayDims: arrayDims,
IsArray: arrayDims > 0,
},
})
case *dw.SetNotNullInfo:
if len(a.Column) == 0 {
continue
}
name := identifier(a.Column[len(a.Column)-1])
subtype := ast.AT_SetNotNull
if a.Drop {
subtype = ast.AT_DropNotNull
}
alter.Cmds.Items = append(alter.Cmds.Items, &ast.AlterTableCmd{
Name: &name,
Subtype: subtype,
})
case *dw.RenameColumnInfo:
if len(a.Column) == 0 {
continue
}
newName := identifier(a.NewName)
stmts = append(stmts, &ast.RenameColumnStmt{
Table: table,
Col: &ast.ColumnRef{
Name: identifier(a.Column[len(a.Column)-1]),
},
NewName: &newName,
MissingOk: n.IfExists,
})
case *dw.RenameInfo:
newName := identifier(a.NewName)
stmts = append(stmts, &ast.RenameTableStmt{
Table: table,
NewName: &newName,
MissingOk: n.IfExists,
})
}
}
if len(alter.Cmds.Items) > 0 {
stmts = append(stmts, alter)
}
switch len(stmts) {
case 0:
return todo(n)
case 1:
return stmts[0]
default:
return &ast.List{Items: stmts}
}
}