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

1399 lines
33 KiB
Go

package clickhouse
import (
"fmt"
"sort"
"strconv"
"strings"
chast "github.com/sqlc-dev/doubleclick/ast"
"github.com/sqlc-dev/doubleclick/token"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
)
type cc struct {
paramCount int
}
// pos is a node's byte offset in the query. doubleclick counts positions
// from one; the rest of sqlc counts from zero.
func pos(n interface{ Pos() token.Position }) int {
if n == nil {
return 0
}
if off := n.Pos().Offset; off > 0 {
return off - 1
}
return 0
}
func (c *cc) convert(node chast.Node) ast.Node {
switch n := node.(type) {
case *chast.SelectWithUnionQuery:
return c.convertSelectWithUnionQuery(n)
case *chast.SelectQuery:
return c.convertSelectQuery(n)
case *chast.InsertQuery:
return c.convertInsertQuery(n)
case *chast.CreateQuery:
return c.convertCreateQuery(n)
case *chast.UpdateQuery:
return c.convertUpdateQuery(n)
case *chast.DeleteQuery:
return c.convertDeleteQuery(n)
case *chast.DropQuery:
return c.convertDropQuery(n)
case *chast.AlterQuery:
return c.convertAlterQuery(n)
case *chast.TruncateQuery:
return c.convertTruncateQuery(n)
default:
return todo(n)
}
}
func (c *cc) convertSelectWithUnionQuery(n *chast.SelectWithUnionQuery) ast.Node {
if len(n.Selects) == 0 {
return &ast.TODO{}
}
// Single select without union
if len(n.Selects) == 1 {
return c.convert(n.Selects[0])
}
// Build a chain of SelectStmt with UNION operations
var result *ast.SelectStmt
for i, sel := range n.Selects {
stmt, ok := c.convert(sel).(*ast.SelectStmt)
if !ok {
continue
}
if i == 0 {
result = stmt
} else {
unionMode := ast.Union
if i-1 < len(n.UnionModes) {
switch strings.ToUpper(n.UnionModes[i-1]) {
case "ALL":
unionMode = ast.Union
case "DISTINCT":
unionMode = ast.Union
}
}
result = &ast.SelectStmt{
Op: unionMode,
All: n.UnionAll || (i-1 < len(n.UnionModes) && strings.ToUpper(n.UnionModes[i-1]) == "ALL"),
Larg: result,
Rarg: stmt,
}
}
}
return result
}
func (c *cc) convertSelectQuery(n *chast.SelectQuery) *ast.SelectStmt {
stmt := &ast.SelectStmt{}
// Convert target list (SELECT columns)
if len(n.Columns) > 0 {
stmt.TargetList = &ast.List{}
for _, col := range n.Columns {
target := c.convertToResTarget(col)
if target != nil {
stmt.TargetList.Items = append(stmt.TargetList.Items, target)
}
}
}
// Convert FROM clause
if n.From != nil {
stmt.FromClause = c.convertTablesInSelectQuery(n.From)
}
// Convert WHERE clause
if n.Where != nil {
stmt.WhereClause = c.convertExpr(n.Where)
}
// Convert GROUP BY clause
if len(n.GroupBy) > 0 {
stmt.GroupClause = &ast.List{}
for _, expr := range n.GroupBy {
stmt.GroupClause.Items = append(stmt.GroupClause.Items, c.convertExpr(expr))
}
}
// Convert HAVING clause
if n.Having != nil {
stmt.HavingClause = c.convertExpr(n.Having)
}
// Convert ORDER BY clause
if len(n.OrderBy) > 0 {
stmt.SortClause = &ast.List{}
for _, orderBy := range n.OrderBy {
stmt.SortClause.Items = append(stmt.SortClause.Items, c.convertOrderByElement(orderBy))
}
}
// Convert LIMIT clause
if n.Limit != nil {
stmt.LimitCount = c.convertExpr(n.Limit)
}
// Convert OFFSET clause
if n.Offset != nil {
stmt.LimitOffset = c.convertExpr(n.Offset)
}
// Convert DISTINCT clause
if n.Distinct {
stmt.DistinctClause = &ast.List{}
}
// Convert DISTINCT ON clause
if len(n.DistinctOn) > 0 {
stmt.DistinctClause = &ast.List{}
for _, expr := range n.DistinctOn {
stmt.DistinctClause.Items = append(stmt.DistinctClause.Items, c.convertExpr(expr))
}
}
// Convert WITH clause (CTEs)
if len(n.With) > 0 {
stmt.WithClause = &ast.WithClause{
Ctes: &ast.List{},
}
for _, cte := range n.With {
var name string
var query chast.Expression
switch w := cte.(type) {
case *chast.WithElement:
// "name AS (SELECT ...)" or a scalar "(expr) AS name".
name, query = w.Name, w.Query
case *chast.AliasedExpr:
name, query = w.Alias, w.Expr
default:
continue
}
cteNode := &ast.CommonTableExpr{Ctename: &name}
if subq, ok := query.(*chast.Subquery); ok {
cteNode.Ctequery = c.convert(subq.Query)
} else {
cteNode.Ctequery = c.convertExpr(query)
}
stmt.WithClause.Ctes.Items = append(stmt.WithClause.Ctes.Items, cteNode)
}
}
return stmt
}
func (c *cc) convertToResTarget(expr chast.Expression) *ast.ResTarget {
res := &ast.ResTarget{
Location: pos(expr),
}
switch e := expr.(type) {
case *chast.Asterisk:
if e.Table != "" {
// table.*
res.Val = &ast.ColumnRef{
Fields: &ast.List{
Items: []ast.Node{
NewIdentifier(e.Table),
&ast.A_Star{},
},
},
}
} else {
// Just *
res.Val = &ast.ColumnRef{
Fields: &ast.List{
Items: []ast.Node{&ast.A_Star{}},
},
}
}
return res
case *chast.AliasedExpr:
res.Name = &e.Alias
res.Val = c.convertExpr(e.Expr)
return res
case *chast.Identifier:
if e.Alias != "" {
res.Name = &e.Alias
}
res.Val = c.convertIdentifier(e)
return res
}
res.Val = c.convertExpr(expr)
if alias := exprAlias(expr); alias != "" {
res.Name = &alias
} else if name := columnName(expr); name != "" {
// ClickHouse names an unaliased expression column after the
// expression itself, written in its canonical function form.
res.Name = &name
}
return res
}
// exprAlias returns the alias the parser attached to an expression, for
// the node kinds that carry one directly.
func exprAlias(expr chast.Expression) string {
switch e := expr.(type) {
case *chast.AliasedExpr:
return e.Alias
case *chast.Identifier:
return e.Alias
case *chast.FunctionCall:
return e.Alias
case *chast.CaseExpr:
return e.Alias
case *chast.CastExpr:
return e.Alias
case *chast.LikeExpr:
return e.Alias
case *chast.ExtractExpr:
return e.Alias
case *chast.Subquery:
return e.Alias
}
return ""
}
// binaryFunctions are the function names ClickHouse gives its operators when
// it names a column after an expression.
var binaryFunctions = map[string]string{
"+": "plus", "-": "minus", "*": "multiply", "/": "divide", "%": "modulo",
"=": "equals", "==": "equals", "!=": "notEquals", "<>": "notEquals",
"<": "less", ">": "greater", "<=": "lessOrEquals", ">=": "greaterOrEquals",
"AND": "and", "OR": "or", "||": "concat",
}
// columnName writes an expression the way ClickHouse names a result column
// that has no alias: a function call with its arguments, an operator as the
// function it stands for, an identifier or literal as written. It returns ""
// for an expression it cannot write, which then keeps sqlc's own name.
func columnName(expr chast.Expression) string {
switch e := expr.(type) {
case *chast.Identifier:
return e.Name()
case *chast.Literal:
return literalText(e)
case *chast.FunctionCall:
if e.Over != nil || e.Filter != nil || e.Distinct {
return ""
}
var params, args []string
for _, p := range e.Parameters {
s := columnName(p)
if s == "" {
return ""
}
params = append(params, s)
}
for _, a := range e.Arguments {
s := columnName(a)
if s == "" {
return ""
}
args = append(args, s)
}
name := e.Name
if len(e.Parameters) > 0 {
name += "(" + strings.Join(params, ", ") + ")"
}
return name + "(" + strings.Join(args, ", ") + ")"
case *chast.BinaryExpr:
fn, ok := binaryFunctions[strings.ToUpper(e.Op)]
if !ok {
return ""
}
left, right := columnName(e.Left), columnName(e.Right)
if left == "" || right == "" {
return ""
}
return fn + "(" + left + ", " + right + ")"
case *chast.UnaryExpr:
operand := columnName(e.Operand)
if operand == "" {
return ""
}
switch strings.ToUpper(e.Op) {
case "-":
return "negate(" + operand + ")"
case "NOT":
return "not(" + operand + ")"
}
return ""
case *chast.IsNullExpr:
arg := columnName(e.Expr)
if arg == "" {
return ""
}
if e.Not {
return "isNotNull(" + arg + ")"
}
return "isNull(" + arg + ")"
case *chast.TernaryExpr:
cond, then, els := columnName(e.Condition), columnName(e.Then), columnName(e.Else)
if cond == "" || then == "" || els == "" {
return ""
}
return "if(" + cond + ", " + then + ", " + els + ")"
}
return ""
}
// literalText writes a literal as ClickHouse prints it in a column name.
func literalText(l *chast.Literal) string {
switch l.Type {
case chast.LiteralString:
return quoteString(fmt.Sprint(l.Value))
case chast.LiteralNull:
return "NULL"
case chast.LiteralBoolean:
return fmt.Sprint(l.Value)
case chast.LiteralInteger, chast.LiteralFloat:
if l.Source != "" {
return l.Source
}
return fmt.Sprint(l.Value)
}
return ""
}
func quoteString(s string) string {
s = strings.ReplaceAll(s, `\`, `\\`)
s = strings.ReplaceAll(s, "'", `\'`)
return "'" + s + "'"
}
func (c *cc) convertTablesInSelectQuery(n *chast.TablesInSelectQuery) *ast.List {
if n == nil || len(n.Tables) == 0 {
return nil
}
result := &ast.List{}
for i, elem := range n.Tables {
if elem.Table != nil {
tableExpr := c.convertTableExpression(elem.Table)
if i == 0 {
result.Items = append(result.Items, tableExpr)
} else if elem.Join != nil {
// This element has a join
joinExpr := c.convertTableJoin(elem.Join, result.Items[len(result.Items)-1], tableExpr)
result.Items[len(result.Items)-1] = joinExpr
} else {
result.Items = append(result.Items, tableExpr)
}
} else if elem.Join != nil && len(result.Items) > 0 {
// Join without table (should not happen normally)
continue
}
}
return result
}
func (c *cc) convertTableExpression(n *chast.TableExpression) ast.Node {
var result ast.Node
switch t := n.Table.(type) {
case *chast.TableIdentifier:
rv := parseTableIdentifierToRangeVar(t)
if n.Alias != "" {
alias := n.Alias
rv.Alias = &ast.Alias{Aliasname: &alias}
}
result = rv
case *chast.Subquery:
subselect := &ast.RangeSubselect{
Subquery: c.convert(t.Query),
}
alias := n.Alias
if alias == "" && t.Alias != "" {
alias = t.Alias
}
if alias != "" {
subselect.Alias = &ast.Alias{Aliasname: &alias}
}
result = subselect
case *chast.FunctionCall:
// Table function like file(), url(), etc.
rf := &ast.RangeFunction{
Functions: &ast.List{
Items: []ast.Node{c.convertFunctionCall(t)},
},
}
if n.Alias != "" {
alias := n.Alias
rf.Alias = &ast.Alias{Aliasname: &alias}
}
result = rf
default:
result = &ast.TODO{}
}
return result
}
func (c *cc) convertTableJoin(n *chast.TableJoin, left, right ast.Node) *ast.JoinExpr {
join := &ast.JoinExpr{
Larg: left,
Rarg: right,
}
// Convert join type
switch n.Type {
case chast.JoinInner:
join.Jointype = ast.JoinTypeInner
case chast.JoinLeft:
join.Jointype = ast.JoinTypeLeft
case chast.JoinRight:
join.Jointype = ast.JoinTypeRight
case chast.JoinFull:
join.Jointype = ast.JoinTypeFull
case chast.JoinCross:
join.Jointype = ast.JoinTypeInner
join.IsNatural = false
default:
join.Jointype = ast.JoinTypeInner
}
// Convert ON clause
if n.On != nil {
join.Quals = c.convertExpr(n.On)
}
// Convert USING clause
if len(n.Using) > 0 {
join.UsingClause = &ast.List{}
for _, u := range n.Using {
if id, ok := u.(*chast.Identifier); ok {
join.UsingClause.Items = append(join.UsingClause.Items, NewIdentifier(id.Name()))
}
}
}
return join
}
func (c *cc) convertExpr(expr chast.Expression) ast.Node {
if expr == nil {
return nil
}
switch e := expr.(type) {
case *chast.Identifier:
return c.convertIdentifier(e)
case *chast.Literal:
return c.convertLiteral(e)
case *chast.BinaryExpr:
return c.convertBinaryExpr(e)
case *chast.FunctionCall:
switch strings.ToLower(e.Name) {
case "coalesce", "ifnull":
// COALESCE is null only when every argument is, which a seeded
// function signature cannot say.
args := &ast.List{}
for _, arg := range e.Arguments {
args.Items = append(args.Items, c.convertExpr(arg))
}
return &ast.CoalesceExpr{Args: args, Location: pos(e)}
}
return c.convertFunctionCall(e)
case *chast.ExistsExpr:
return &ast.SubLink{
SubLinkType: ast.EXISTS_SUBLINK,
Subselect: c.convert(e.Query),
Location: pos(e),
}
case *chast.AliasedExpr:
return c.convertExpr(e.Expr)
case *chast.Parameter:
return c.convertParameter(e)
case *chast.Asterisk:
return c.convertAsterisk(e)
case *chast.CaseExpr:
return c.convertCaseExpr(e)
case *chast.CastExpr:
return c.convertCastExpr(e)
case *chast.BetweenExpr:
return c.convertBetweenExpr(e)
case *chast.InExpr:
return c.convertInExpr(e)
case *chast.IsNullExpr:
return c.convertIsNullExpr(e)
case *chast.LikeExpr:
return c.convertLikeExpr(e)
case *chast.Subquery:
return c.convertSubquery(e)
case *chast.ArrayAccess:
return c.convertArrayAccess(e)
case *chast.UnaryExpr:
return c.convertUnaryExpr(e)
case *chast.Lambda:
// Lambda expressions are ClickHouse-specific, return as-is for now
return &ast.TODO{}
default:
return &ast.TODO{}
}
}
func (c *cc) convertIdentifier(n *chast.Identifier) *ast.ColumnRef {
fields := &ast.List{}
for _, part := range n.Parts {
fields.Items = append(fields.Items, NewIdentifier(part))
}
return &ast.ColumnRef{
Fields: fields,
Location: pos(n),
}
}
func (c *cc) convertLiteral(n *chast.Literal) *ast.A_Const {
switch n.Type {
case chast.LiteralString:
str := n.Value.(string)
return &ast.A_Const{
Val: &ast.String{Str: str},
Location: pos(n),
}
case chast.LiteralInteger:
var ival int64
switch v := n.Value.(type) {
case int64:
ival = v
case int:
ival = int64(v)
case float64:
ival = int64(v)
case string:
ival, _ = strconv.ParseInt(v, 10, 64)
}
return &ast.A_Const{
Val: &ast.Integer{Ival: ival},
Location: pos(n),
}
case chast.LiteralFloat:
var fval float64
switch v := n.Value.(type) {
case float64:
fval = v
case string:
fval, _ = strconv.ParseFloat(v, 64)
}
str := strconv.FormatFloat(fval, 'f', -1, 64)
return &ast.A_Const{
Val: &ast.Float{Str: str},
Location: pos(n),
}
case chast.LiteralBoolean:
// ClickHouse booleans are typically 0/1
bval := n.Value.(bool)
if bval {
return &ast.A_Const{
Val: &ast.Integer{Ival: 1},
Location: pos(n),
}
}
return &ast.A_Const{
Val: &ast.Integer{Ival: 0},
Location: pos(n),
}
case chast.LiteralNull:
return &ast.A_Const{
Val: &ast.Null{},
Location: pos(n),
}
default:
return &ast.A_Const{
Location: pos(n),
}
}
}
func (c *cc) convertBinaryExpr(n *chast.BinaryExpr) ast.Node {
op := strings.ToUpper(n.Op)
// Handle logical operators
if op == "AND" || op == "OR" {
var boolop ast.BoolExprType
if op == "AND" {
boolop = ast.BoolExprTypeAnd
} else {
boolop = ast.BoolExprTypeOr
}
return &ast.BoolExpr{
Boolop: boolop,
Args: &ast.List{
Items: []ast.Node{
c.convertExpr(n.Left),
c.convertExpr(n.Right),
},
},
Location: pos(n),
}
}
// Handle other operators
return &ast.A_Expr{
Kind: ast.A_Expr_Kind_OP,
Name: &ast.List{
Items: []ast.Node{&ast.String{Str: n.Op}},
},
Lexpr: c.convertExpr(n.Left),
Rexpr: c.convertExpr(n.Right),
Location: pos(n),
}
}
func (c *cc) convertFunctionCall(n *chast.FunctionCall) *ast.FuncCall {
fc := &ast.FuncCall{
Funcname: &ast.List{
Items: []ast.Node{&ast.String{Str: n.Name}},
},
Location: pos(n),
AggDistinct: n.Distinct,
}
// Convert arguments
if len(n.Arguments) > 0 {
fc.Args = &ast.List{}
for _, arg := range n.Arguments {
fc.Args.Items = append(fc.Args.Items, c.convertExpr(arg))
}
}
// Convert window function
if n.Over != nil {
fc.Over = &ast.WindowDef{}
if len(n.Over.PartitionBy) > 0 {
fc.Over.PartitionClause = &ast.List{}
for _, p := range n.Over.PartitionBy {
fc.Over.PartitionClause.Items = append(fc.Over.PartitionClause.Items, c.convertExpr(p))
}
}
if len(n.Over.OrderBy) > 0 {
fc.Over.OrderClause = &ast.List{}
for _, o := range n.Over.OrderBy {
fc.Over.OrderClause.Items = append(fc.Over.OrderClause.Items, c.convertOrderByElement(o))
}
}
}
return fc
}
func (c *cc) convertParameter(n *chast.Parameter) ast.Node {
c.paramCount++
ref := &ast.ParamRef{
Number: c.paramCount,
Name: n.Name,
Location: pos(n),
}
// A parameter written {name:Type} declares its type, which is what a
// cast of a placeholder says.
if n.Type != nil {
spelling := renderDataType(n.Type)
base, _, _ := unwrapTypeString(spelling)
return &ast.TypeCast{
Arg: ref,
TypeName: &ast.TypeName{Name: base, Spelling: spelling, Canonical: canonicalDataType(n.Type)},
Location: pos(n),
}
}
return ref
}
func (c *cc) convertAsterisk(n *chast.Asterisk) *ast.ColumnRef {
fields := &ast.List{}
if n.Table != "" {
fields.Items = append(fields.Items, NewIdentifier(n.Table))
}
fields.Items = append(fields.Items, &ast.A_Star{})
return &ast.ColumnRef{
Fields: fields,
Location: pos(n),
}
}
func (c *cc) convertCaseExpr(n *chast.CaseExpr) *ast.CaseExpr {
ce := &ast.CaseExpr{
Location: pos(n),
}
// Convert test expression (CASE expr WHEN ...)
if n.Operand != nil {
ce.Arg = c.convertExpr(n.Operand)
}
// Convert WHEN clauses
if len(n.Whens) > 0 {
ce.Args = &ast.List{}
for _, when := range n.Whens {
caseWhen := &ast.CaseWhen{
Expr: c.convertExpr(when.Condition),
Result: c.convertExpr(when.Result),
}
ce.Args.Items = append(ce.Args.Items, caseWhen)
}
}
// Convert ELSE clause
if n.Else != nil {
ce.Defresult = c.convertExpr(n.Else)
}
return ce
}
func (c *cc) convertCastExpr(n *chast.CastExpr) *ast.TypeCast {
tc := &ast.TypeCast{
Arg: c.convertExpr(n.Expr),
Location: pos(n),
}
if n.Type != nil {
// The whole type is handed over as its spelling, arguments and
// nesting included, the way a column's is.
spelling := renderDataType(n.Type)
base, _, _ := unwrapTypeString(spelling)
tc.TypeName = &ast.TypeName{Name: base, Spelling: spelling, Canonical: canonicalDataType(n.Type)}
}
return tc
}
func (c *cc) convertBetweenExpr(n *chast.BetweenExpr) *ast.BetweenExpr {
return &ast.BetweenExpr{
Expr: c.convertExpr(n.Expr),
Left: c.convertExpr(n.Low),
Right: c.convertExpr(n.High),
Not: n.Not,
Location: pos(n),
}
}
func (c *cc) convertInExpr(n *chast.InExpr) *ast.In {
in := &ast.In{
Expr: c.convertExpr(n.Expr),
Not: n.Not,
Location: pos(n),
}
// Convert the list
if len(n.List) > 0 {
in.List = make([]ast.Node, 0, len(n.List))
for _, item := range n.List {
in.List = append(in.List, c.convertExpr(item))
}
}
// Handle subquery
if n.Query != nil {
in.Sel = c.convert(n.Query)
}
return in
}
func (c *cc) convertIsNullExpr(n *chast.IsNullExpr) *ast.NullTest {
nullTest := &ast.NullTest{
Arg: c.convertExpr(n.Expr),
Location: pos(n),
}
if n.Not {
nullTest.Nulltesttype = ast.NullTestTypeIsNotNull
} else {
nullTest.Nulltesttype = ast.NullTestTypeIsNull
}
return nullTest
}
func (c *cc) convertLikeExpr(n *chast.LikeExpr) *ast.A_Expr {
kind := ast.A_Expr_Kind(0)
opName := "~~"
if n.CaseInsensitive {
opName = "~~*"
}
if n.Not {
opName = "!~~"
if n.CaseInsensitive {
opName = "!~~*"
}
}
return &ast.A_Expr{
Kind: kind,
Name: &ast.List{
Items: []ast.Node{&ast.String{Str: opName}},
},
Lexpr: c.convertExpr(n.Expr),
Rexpr: c.convertExpr(n.Pattern),
Location: pos(n),
}
}
// convertSubquery converts a subquery used as a value: a scalar subquery.
// EXISTS is its own node.
func (c *cc) convertSubquery(n *chast.Subquery) *ast.SubLink {
return &ast.SubLink{
SubLinkType: ast.EXPR_SUBLINK,
Subselect: c.convert(n.Query),
Location: pos(n),
}
}
func (c *cc) convertArrayAccess(n *chast.ArrayAccess) *ast.A_Indirection {
return &ast.A_Indirection{
Arg: c.convertExpr(n.Array),
Indirection: &ast.List{
Items: []ast.Node{
&ast.A_Indices{
Uidx: c.convertExpr(n.Index),
},
},
},
}
}
func (c *cc) convertUnaryExpr(n *chast.UnaryExpr) ast.Node {
op := strings.ToUpper(n.Op)
if op == "NOT" {
return &ast.BoolExpr{
Boolop: ast.BoolExprTypeNot,
Args: &ast.List{
Items: []ast.Node{c.convertExpr(n.Operand)},
},
Location: pos(n),
}
}
return &ast.A_Expr{
Kind: ast.A_Expr_Kind_OP,
Name: &ast.List{
Items: []ast.Node{&ast.String{Str: n.Op}},
},
Rexpr: c.convertExpr(n.Operand),
Location: pos(n),
}
}
func (c *cc) convertOrderByElement(n *chast.OrderByElement) *ast.SortBy {
sortBy := &ast.SortBy{
Node: c.convertExpr(n.Expression),
Location: pos(n.Expression),
}
if n.Descending {
sortBy.SortbyDir = ast.SortByDirDesc
} else {
sortBy.SortbyDir = ast.SortByDirAsc
}
if n.NullsFirst != nil {
if *n.NullsFirst {
sortBy.SortbyNulls = ast.SortByNullsFirst
} else {
sortBy.SortbyNulls = ast.SortByNullsLast
}
}
return sortBy
}
func (c *cc) convertInsertQuery(n *chast.InsertQuery) *ast.InsertStmt {
stmt := &ast.InsertStmt{
Relation: &ast.RangeVar{
Relname: &n.Table,
},
}
if n.Database != "" {
stmt.Relation.Schemaname = &n.Database
}
// Convert column list
if len(n.Columns) > 0 {
stmt.Cols = &ast.List{}
for _, col := range n.Columns {
name := col.Name()
stmt.Cols.Items = append(stmt.Cols.Items, &ast.ResTarget{
Name: &name,
})
}
}
// Convert SELECT subquery if present
if n.Select != nil {
stmt.SelectStmt = c.convert(n.Select)
}
// Convert VALUES clause
if len(n.Values) > 0 {
selectStmt := &ast.SelectStmt{
ValuesLists: &ast.List{},
}
for _, row := range n.Values {
rowList := &ast.List{}
for _, val := range row {
rowList.Items = append(rowList.Items, c.convertExpr(val))
}
selectStmt.ValuesLists.Items = append(selectStmt.ValuesLists.Items, rowList)
}
stmt.SelectStmt = selectStmt
}
return stmt
}
func (c *cc) convertCreateQuery(n *chast.CreateQuery) ast.Node {
// Handle CREATE DATABASE
if n.CreateDatabase {
return &ast.CreateSchemaStmt{
Name: &n.Database,
IfNotExists: n.IfNotExists,
}
}
// Handle CREATE TABLE
if n.Table != "" {
stmt := &ast.CreateTableStmt{
Name: &ast.TableName{
Name: identifier(n.Table),
},
IfNotExists: n.IfNotExists,
}
if n.Database != "" {
stmt.Name.Schema = identifier(n.Database)
}
// Convert columns. A Nested column is what ClickHouse stores as
// one array column per element, named n.a, and reports as those.
for _, col := range n.Columns {
if cols, ok := c.convertNestedColumn(col); ok {
stmt.Cols = append(stmt.Cols, cols...)
continue
}
colDef := c.convertColumnDeclaration(col)
stmt.Cols = append(stmt.Cols, colDef)
}
// Convert AS SELECT
if n.AsSelect != nil {
// This is a CREATE TABLE ... AS SELECT
// The AsSelect field contains the SELECT statement
}
return stmt
}
// Handle CREATE VIEW
if n.View != "" {
return &ast.ViewStmt{
View: &ast.RangeVar{
Relname: &n.View,
},
Query: c.convert(n.AsSelect),
Replace: n.OrReplace,
}
}
return &ast.TODO{}
}
// convertNestedColumn expands a column declared Nested(a T, b U) into the
// columns n.a Array(T) and n.b Array(U), which is how ClickHouse's
// system.columns lists it and what a query selects.
func (c *cc) convertNestedColumn(n *chast.ColumnDeclaration) ([]*ast.ColumnDef, bool) {
if n.Type == nil || !strings.EqualFold(n.Type.Name, "Nested") {
return nil, false
}
var cols []*ast.ColumnDef
for _, p := range n.Type.Parameters {
pair, ok := p.(*chast.NameTypePair)
if !ok {
continue
}
spelling := "Array(" + renderDataType(pair.Type) + ")"
cols = append(cols, &ast.ColumnDef{
Colname: identifier(n.Name) + "." + identifier(pair.Name),
TypeName: &ast.TypeName{Name: "array", Spelling: spelling, Canonical: "Array(" + canonicalDataType(pair.Type) + ")"},
IsArray: true,
IsNotNull: true,
})
}
return cols, len(cols) > 0
}
func (c *cc) convertColumnDeclaration(n *chast.ColumnDeclaration) *ast.ColumnDef {
colDef := &ast.ColumnDef{
Colname: identifier(n.Name),
IsNotNull: isNotNull(n),
}
if n.Type != nil {
spelling := renderDataType(n.Type)
base, isArray, nullable := unwrapTypeString(spelling)
// The catalog resolves the base type; the full spelling, with its
// arguments and nesting, is kept for the analysis to report.
colDef.TypeName = &ast.TypeName{Name: base, Spelling: spelling, Canonical: canonicalDataType(n.Type)}
colDef.IsArray = isArray
if nullable {
colDef.IsNotNull = false
}
}
// Handle PRIMARY KEY constraint
if n.PrimaryKey {
colDef.PrimaryKey = true
}
// Handle DEFAULT
if n.Default != nil {
// colDef.RawDefault = c.convertExpr(n.Default)
}
// Handle comment
if n.Comment != "" {
colDef.Comment = n.Comment
}
return colDef
}
// renderDataType spells a type as the author wrote it, which the formatter
// prints back.
func renderDataType(dt *chast.DataType) string {
return renderType(dt, false)
}
// canonicalDataType spells a type the way ClickHouse stores it, which the
// analysis core reads: an Enum's members are numbered and the family
// sized by their count, a Variant's members are sorted, and a named
// element is written label first, so that Enum('a', 'b') is Enum8('a' =
// 1, 'b' = 2), Variant(String, Int64) is Variant(Int64, String) and
// Tuple(lat Float64) is Tuple(lat: Float64).
func canonicalDataType(dt *chast.DataType) string {
return renderType(dt, true)
}
func renderType(dt *chast.DataType, canonical bool) string {
if dt == nil {
return ""
}
if len(dt.Parameters) == 0 {
return dt.Name
}
name := dt.Name
parts := make([]string, 0, len(dt.Parameters))
switch lower := strings.ToLower(name); {
case canonical && (lower == "enum" || lower == "enum8" || lower == "enum16"):
if lower == "enum" {
name = "Enum8"
if len(dt.Parameters) > 127 {
name = "Enum16"
}
}
next := int64(1)
for _, p := range dt.Parameters {
switch v := p.(type) {
case *chast.BinaryExpr:
// 'a' = 3 numbers itself, and the next bare member follows it.
parts = append(parts, renderParam(v, canonical))
if lit, ok := v.Right.(*chast.Literal); ok {
if n, err := strconv.ParseInt(fmt.Sprint(lit.Value), 10, 64); err == nil {
next = n + 1
}
}
default:
parts = append(parts, renderParam(p, canonical)+" = "+strconv.FormatInt(next, 10))
next++
}
}
case canonical && lower == "lowcardinality" && len(dt.Parameters) == 1:
// ClickHouse spells a nullable low-cardinality column as
// LowCardinality(Nullable(T)), the only order it accepts. The
// nullability is the column's, so the canonical form is
// Nullable(LowCardinality(T)), which reads as a nullable
// LowCardinality(T).
if inner, ok := dt.Parameters[0].(*chast.DataType); ok && strings.EqualFold(inner.Name, "nullable") && len(inner.Parameters) == 1 {
return "Nullable(LowCardinality(" + renderParam(inner.Parameters[0], canonical) + "))"
}
parts = append(parts, renderParam(dt.Parameters[0], canonical))
case canonical && lower == "variant":
for _, p := range dt.Parameters {
parts = append(parts, renderParam(p, canonical))
}
sort.Strings(parts)
default:
for _, p := range dt.Parameters {
parts = append(parts, renderParam(p, canonical))
}
}
return name + "(" + strings.Join(parts, ", ") + ")"
}
func renderTypeParam(e chast.Expression) string {
return renderParam(e, false)
}
func renderParam(e chast.Expression, canonical bool) string {
switch v := e.(type) {
case *chast.DataType:
return renderType(v, canonical)
case *chast.Literal:
if v.Type == chast.LiteralString {
return quoteString(fmt.Sprint(v.Value))
}
if v.Source != "" {
return v.Source
}
return fmt.Sprintf("%v", v.Value)
case *chast.Identifier:
return strings.Join(v.Parts, ".")
case *chast.NameTypePair:
// A named tuple or nested element: `lat Float64`, or `lat: Float64`
// in the canonical form.
if canonical {
return v.Name + ": " + renderType(v.Type, canonical)
}
return v.Name + " " + renderType(v.Type, canonical)
case *chast.BinaryExpr:
// An enum member: `'active' = 1`.
return renderParam(v.Left, canonical) + " " + v.Op + " " + renderParam(v.Right, canonical)
default:
return ""
}
}
func unwrapTypeString(s string) (name string, isArray, nullable bool) {
base, args := splitType(s)
switch strings.ToLower(base) {
case "nullable":
if len(args) == 1 {
inner, arr, _ := unwrapTypeString(args[0])
return inner, arr, true
}
return strings.ToLower(base), false, true
case "lowcardinality":
// LowCardinality is an encoding of the type it wraps, and a
// column of LowCardinality(Nullable(String)) holds NULLs.
if len(args) == 1 {
return unwrapTypeString(args[0])
}
return strings.ToLower(base), false, false
case "array":
if len(args) == 1 {
inner, _, _ := unwrapTypeString(args[0])
return inner, true, false
}
return strings.ToLower(base), true, false
default:
if strings.TrimSpace(base) == "" {
return "nothing", false, false
}
return strings.ToLower(base), false, false
}
}
func splitType(s string) (base string, args []string) {
s = strings.TrimSpace(s)
open := strings.IndexByte(s, '(')
if open < 0 || !strings.HasSuffix(s, ")") {
return s, nil
}
base = strings.TrimSpace(s[:open])
inner := s[open+1 : len(s)-1]
depth, start := 0, 0
for i := 0; i < len(inner); i++ {
switch inner[i] {
case '(':
depth++
case ')':
depth--
case ',':
if depth == 0 {
args = append(args, strings.TrimSpace(inner[start:i]))
start = i + 1
}
}
}
if last := strings.TrimSpace(inner[start:]); last != "" {
args = append(args, last)
}
return base, args
}
func (c *cc) convertUpdateQuery(n *chast.UpdateQuery) *ast.UpdateStmt {
rv := &ast.RangeVar{
Relname: &n.Table,
}
if n.Database != "" {
rv.Schemaname = &n.Database
}
stmt := &ast.UpdateStmt{
Relations: &ast.List{
Items: []ast.Node{rv},
},
}
// Convert assignments
if len(n.Assignments) > 0 {
stmt.TargetList = &ast.List{}
for _, assign := range n.Assignments {
name := identifier(assign.Column)
stmt.TargetList.Items = append(stmt.TargetList.Items, &ast.ResTarget{
Name: &name,
Val: c.convertExpr(assign.Value),
})
}
}
// Convert WHERE clause
if n.Where != nil {
stmt.WhereClause = c.convertExpr(n.Where)
}
return stmt
}
func (c *cc) convertDeleteQuery(n *chast.DeleteQuery) *ast.DeleteStmt {
rv := &ast.RangeVar{
Relname: &n.Table,
}
if n.Database != "" {
rv.Schemaname = &n.Database
}
stmt := &ast.DeleteStmt{
Relations: &ast.List{
Items: []ast.Node{rv},
},
}
// Convert WHERE clause
if n.Where != nil {
stmt.WhereClause = c.convertExpr(n.Where)
}
return stmt
}
func (c *cc) convertDropQuery(n *chast.DropQuery) ast.Node {
// Handle DROP TABLE
if n.Table != "" {
tableName := &ast.TableName{
Name: identifier(n.Table),
}
if n.Database != "" {
tableName.Schema = identifier(n.Database)
}
return &ast.DropTableStmt{
IfExists: n.IfExists,
Tables: []*ast.TableName{tableName},
}
}
// Handle DROP TABLE with multiple tables
if len(n.Tables) > 0 {
tables := make([]*ast.TableName, 0, len(n.Tables))
for _, t := range n.Tables {
tables = append(tables, parseTableName(t))
}
return &ast.DropTableStmt{
IfExists: n.IfExists,
Tables: tables,
}
}
// Handle DROP DATABASE - return TODO for now
// Handle DROP VIEW - return TODO for now
return &ast.TODO{}
}
func (c *cc) convertAlterQuery(n *chast.AlterQuery) ast.Node {
alt := &ast.AlterTableStmt{
Table: &ast.TableName{
Name: identifier(n.Table),
},
Cmds: &ast.List{},
}
if n.Database != "" {
alt.Table.Schema = identifier(n.Database)
}
for _, cmd := range n.Commands {
switch cmd.Type {
case chast.AlterAddColumn:
if cmd.Column != nil {
name := cmd.Column.Name
alt.Cmds.Items = append(alt.Cmds.Items, &ast.AlterTableCmd{
Name: &name,
Subtype: ast.AT_AddColumn,
Def: c.convertColumnDeclaration(cmd.Column),
})
}
case chast.AlterDropColumn:
name := cmd.ColumnName
alt.Cmds.Items = append(alt.Cmds.Items, &ast.AlterTableCmd{
Name: &name,
Subtype: ast.AT_DropColumn,
MissingOk: cmd.IfExists,
})
case chast.AlterModifyColumn:
if cmd.Column != nil {
name := cmd.Column.Name
// Drop and re-add to simulate modify
alt.Cmds.Items = append(alt.Cmds.Items, &ast.AlterTableCmd{
Name: &name,
Subtype: ast.AT_DropColumn,
})
alt.Cmds.Items = append(alt.Cmds.Items, &ast.AlterTableCmd{
Name: &name,
Subtype: ast.AT_AddColumn,
Def: c.convertColumnDeclaration(cmd.Column),
})
}
case chast.AlterRenameColumn:
oldName := cmd.ColumnName
newName := cmd.NewName
return &ast.RenameColumnStmt{
Table: alt.Table,
Col: &ast.ColumnRef{Name: oldName},
NewName: &newName,
}
}
}
return alt
}
func (c *cc) convertTruncateQuery(n *chast.TruncateQuery) *ast.TruncateStmt {
stmt := &ast.TruncateStmt{
Relations: &ast.List{},
}
tableName := n.Table
schemaName := n.Database
rv := &ast.RangeVar{
Relname: &tableName,
}
if schemaName != "" {
rv.Schemaname = &schemaName
}
stmt.Relations.Items = append(stmt.Relations.Items, rv)
return stmt
}