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

1200 lines
32 KiB
Go

package mssql
import (
"strconv"
"strings"
tsql "github.com/sqlc-dev/teesql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
)
type cc struct {
paramCount int
// namedParams tracks the number assigned to each "@name" so repeated
// uses share a single parameter.
namedParams map[string]int
// toByte maps a teesql UTF-16 code-unit offset to a byte offset in the
// source, so Location fields agree with StmtLocation/StmtLen.
toByte func(int) int
}
// loc returns a node's start offset in bytes, for the Location fields of the
// sqlc AST.
func (c *cc) loc(n any) int {
f, ok := n.(fragmented)
if !ok {
return 0
}
off := f.Frag().StartOffset
if c.toByte != nil {
return c.toByte(off)
}
return off
}
func (c *cc) parseRangeVar(n *tsql.SchemaObjectName) *ast.RangeVar {
name := parseTableName(n)
rv := &ast.RangeVar{
Relname: &name.Name,
Location: c.loc(n),
}
if name.Schema != "" {
rv.Schemaname = &name.Schema
}
return rv
}
func (c *cc) convert(node tsql.Node) ast.Node {
switch n := node.(type) {
case *tsql.SelectStatement:
return c.convertSelectStatement(n)
case *tsql.InsertStatement:
return c.convertInsertStatement(n)
case *tsql.UpdateStatement:
return c.convertUpdateStatement(n)
case *tsql.DeleteStatement:
return c.convertDeleteStatement(n)
case *tsql.CreateTableStatement:
return c.convertCreateTableStatement(n)
case *tsql.DropTableStatement:
return c.convertDropTableStatement(n)
case *tsql.AlterTableAddTableElementStatement:
return c.convertAlterTableAddTableElementStatement(n)
case *tsql.AlterTableDropTableElementStatement:
return c.convertAlterTableDropTableElementStatement(n)
case *tsql.CreateTypeUddtStatement:
return c.convertCreateTypeUddtStatement(n)
default:
return todo(n)
}
}
// convertCreateTypeUddtStatement reports CREATE TYPE name FROM base [NOT
// NULL] as the domain it is: a type standing on its base that may forbid
// NULL.
func (c *cc) convertCreateTypeUddtStatement(n *tsql.CreateTypeUddtStatement) ast.Node {
if n.Name == nil || n.Name.BaseIdentifier == nil || n.DataType == nil {
return todo(n)
}
stmt := &ast.CreateDomainStmt{
Domainname: &ast.List{},
TypeName: dataTypeName(n.DataType),
}
if n.Name.SchemaIdentifier != nil {
stmt.Domainname.Items = append(stmt.Domainname.Items, NewIdentifier(identifierValue(n.Name.SchemaIdentifier)))
}
stmt.Domainname.Items = append(stmt.Domainname.Items, NewIdentifier(identifierValue(n.Name.BaseIdentifier)))
if n.NullableConstraint != nil && !n.NullableConstraint.Nullable {
stmt.Constraints = &ast.List{Items: []ast.Node{&ast.Constraint{Contype: ast.ConstrTypeNotNull}}}
}
return stmt
}
func (c *cc) convertSelectStatement(n *tsql.SelectStatement) ast.Node {
stmt := c.convertQueryExpression(n.QueryExpression)
sel, ok := stmt.(*ast.SelectStmt)
if !ok {
return stmt
}
if with := c.convertWithClause(n.WithCtesAndXmlNamespaces); with != nil {
sel.WithClause = with
}
return sel
}
func (c *cc) convertWithClause(n *tsql.WithCtesAndXmlNamespaces) *ast.WithClause {
if n == nil || len(n.CommonTableExpressions) == 0 {
return nil
}
with := &ast.WithClause{
Ctes: &ast.List{},
Location: c.loc(n),
}
for _, cte := range n.CommonTableExpressions {
name := identifierValue(cte.ExpressionName)
item := &ast.CommonTableExpr{
Ctename: &name,
Ctequery: c.convertQueryExpression(cte.QueryExpression),
Location: c.loc(cte),
}
if len(cte.Columns) > 0 {
item.Aliascolnames = &ast.List{}
for _, col := range cte.Columns {
item.Aliascolnames.Items = append(item.Aliascolnames.Items, NewIdentifier(col.Value))
}
}
with.Ctes.Items = append(with.Ctes.Items, item)
}
return with
}
func (c *cc) convertQueryExpression(q tsql.QueryExpression) ast.Node {
switch n := q.(type) {
case *tsql.QuerySpecification:
return c.convertQuerySpecification(n)
case *tsql.BinaryQueryExpression:
return c.convertBinaryQueryExpression(n)
case *tsql.QueryParenthesisExpression:
return c.convertQueryExpression(n.QueryExpression)
default:
return todo(q)
}
}
func (c *cc) convertBinaryQueryExpression(n *tsql.BinaryQueryExpression) ast.Node {
var op ast.SetOperation
switch n.BinaryQueryExpressionType {
case "Union":
op = ast.Union
case "Except":
op = ast.Except
case "Intersect":
op = ast.Intersect
default:
return todo(n)
}
larg, ok := c.convertQueryExpression(n.FirstQueryExpression).(*ast.SelectStmt)
if !ok {
return todo(n)
}
rarg, ok := c.convertQueryExpression(n.SecondQueryExpression).(*ast.SelectStmt)
if !ok {
return todo(n)
}
stmt := &ast.SelectStmt{
Op: op,
All: n.All,
Larg: larg,
Rarg: rarg,
}
if n.OrderByClause != nil {
stmt.SortClause = c.convertOrderByClause(n.OrderByClause)
}
return stmt
}
func (c *cc) convertQuerySpecification(n *tsql.QuerySpecification) *ast.SelectStmt {
stmt := &ast.SelectStmt{}
if len(n.SelectElements) > 0 {
stmt.TargetList = &ast.List{}
for _, elem := range n.SelectElements {
if target := c.convertSelectElement(elem); target != nil {
stmt.TargetList.Items = append(stmt.TargetList.Items, target)
}
}
}
if n.FromClause != nil {
stmt.FromClause = &ast.List{}
for _, ref := range n.FromClause.TableReferences {
stmt.FromClause.Items = append(stmt.FromClause.Items, c.convertTableReference(ref))
}
}
if n.WhereClause != nil {
stmt.WhereClause = c.convertBooleanExpression(n.WhereClause.SearchCondition)
}
if n.GroupByClause != nil {
stmt.GroupClause = &ast.List{}
for _, spec := range n.GroupByClause.GroupingSpecifications {
if g, ok := spec.(*tsql.ExpressionGroupingSpecification); ok {
stmt.GroupClause.Items = append(stmt.GroupClause.Items, c.convertScalarExpression(g.Expression))
}
}
}
if n.HavingClause != nil {
stmt.HavingClause = c.convertBooleanExpression(n.HavingClause.SearchCondition)
}
if n.OrderByClause != nil {
stmt.SortClause = c.convertOrderByClause(n.OrderByClause)
}
// OFFSET n ROWS FETCH NEXT m ROWS ONLY
if n.OffsetClause != nil {
if n.OffsetClause.OffsetExpression != nil {
stmt.LimitOffset = c.convertScalarExpression(n.OffsetClause.OffsetExpression)
}
if n.OffsetClause.FetchExpression != nil {
stmt.LimitCount = c.convertScalarExpression(n.OffsetClause.FetchExpression)
}
}
// TOP n; TOP n PERCENT does not translate to a row count.
if n.TopRowFilter != nil && !n.TopRowFilter.Percent {
stmt.LimitCount = c.convertScalarExpression(n.TopRowFilter.Expression)
}
if n.UniqueRowFilter == "Distinct" {
stmt.DistinctClause = &ast.List{}
}
return stmt
}
func (c *cc) convertSelectElement(elem tsql.SelectElement) ast.Node {
switch e := elem.(type) {
case *tsql.SelectScalarExpression:
res := &ast.ResTarget{
Val: c.convertScalarExpression(e.Expression),
Location: c.loc(e),
}
if name := columnAlias(e.ColumnName); name != "" {
res.Name = &name
}
return res
case *tsql.SelectStarExpression:
fields := &ast.List{}
if e.Qualifier != nil {
for _, id := range e.Qualifier.Identifiers {
fields.Items = append(fields.Items, NewIdentifier(id.Value))
}
}
fields.Items = append(fields.Items, &ast.A_Star{})
return &ast.ResTarget{
Val: &ast.ColumnRef{
Fields: fields,
Location: c.loc(e),
},
Location: c.loc(e),
}
default:
return &ast.ResTarget{
Val: todo(elem),
Location: c.loc(elem),
}
}
}
func columnAlias(n *tsql.IdentifierOrValueExpression) string {
if n == nil {
return ""
}
if n.Identifier != nil {
return identifierValue(n.Identifier)
}
if n.Value != "" {
return identifier(n.Value)
}
return ""
}
func (c *cc) convertOrderByClause(n *tsql.OrderByClause) *ast.List {
list := &ast.List{}
for _, elem := range n.OrderByElements {
sortBy := &ast.SortBy{
Node: c.convertScalarExpression(elem.Expression),
Location: c.loc(elem),
}
switch elem.SortOrder {
case "Descending":
sortBy.SortbyDir = ast.SortByDirDesc
case "Ascending":
sortBy.SortbyDir = ast.SortByDirAsc
default:
sortBy.SortbyDir = ast.SortByDirDefault
}
list.Items = append(list.Items, sortBy)
}
return list
}
func (c *cc) convertTableReference(ref tsql.TableReference) ast.Node {
switch t := ref.(type) {
case *tsql.NamedTableReference:
rv := c.parseRangeVar(t.SchemaObject)
if t.Alias != nil {
alias := identifierValue(t.Alias)
rv.Alias = &ast.Alias{Aliasname: &alias}
}
return rv
case *tsql.QueryDerivedTable:
sub := &ast.RangeSubselect{
Subquery: c.convertQueryExpression(t.QueryExpression),
}
if t.Alias != nil {
alias := identifierValue(t.Alias)
sub.Alias = &ast.Alias{Aliasname: &alias}
}
return sub
case *tsql.QualifiedJoin:
join := &ast.JoinExpr{
Larg: c.convertTableReference(t.FirstTableReference),
Rarg: c.convertTableReference(t.SecondTableReference),
}
switch t.QualifiedJoinType {
case "LeftOuter":
join.Jointype = ast.JoinTypeLeft
case "RightOuter":
join.Jointype = ast.JoinTypeRight
case "FullOuter":
join.Jointype = ast.JoinTypeFull
default:
join.Jointype = ast.JoinTypeInner
}
if t.SearchCondition != nil {
join.Quals = c.convertBooleanExpression(t.SearchCondition)
}
return join
case *tsql.UnqualifiedJoin:
if t.UnqualifiedJoinType != "CrossJoin" {
return todo(ref)
}
return &ast.JoinExpr{
Jointype: ast.JoinTypeInner,
Larg: c.convertTableReference(t.FirstTableReference),
Rarg: c.convertTableReference(t.SecondTableReference),
}
default:
return todo(ref)
}
}
func (c *cc) convertBooleanExpression(expr tsql.BooleanExpression) ast.Node {
if expr == nil {
return nil
}
switch e := expr.(type) {
case *tsql.BooleanComparisonExpression:
return &ast.A_Expr{
Kind: ast.A_Expr_Kind_OP,
Name: &ast.List{
Items: []ast.Node{&ast.String{Str: comparisonOperator(e.ComparisonType)}},
},
Lexpr: c.convertScalarExpression(e.FirstExpression),
Rexpr: c.convertScalarExpression(e.SecondExpression),
Location: c.loc(e),
}
case *tsql.BooleanBinaryExpression:
boolop := ast.BoolExprTypeAnd
if e.BinaryExpressionType == "Or" {
boolop = ast.BoolExprTypeOr
}
return &ast.BoolExpr{
Boolop: boolop,
Args: &ast.List{
Items: []ast.Node{
c.convertBooleanExpression(e.FirstExpression),
c.convertBooleanExpression(e.SecondExpression),
},
},
Location: c.loc(e),
}
case *tsql.BooleanNotExpression:
return &ast.BoolExpr{
Boolop: ast.BoolExprTypeNot,
Args: &ast.List{
Items: []ast.Node{c.convertBooleanExpression(e.Expression)},
},
Location: c.loc(e),
}
case *tsql.BooleanParenthesisExpression:
return c.convertBooleanExpression(e.Expression)
case *tsql.BooleanIsNullExpression:
test := ast.NullTestTypeIsNull
if e.IsNot {
test = ast.NullTestTypeIsNotNull
}
return &ast.NullTest{
Arg: c.convertScalarExpression(e.Expression),
Nulltesttype: test,
Location: c.loc(e),
}
case *tsql.BooleanTernaryExpression:
return &ast.BetweenExpr{
Expr: c.convertScalarExpression(e.FirstExpression),
Left: c.convertScalarExpression(e.SecondExpression),
Right: c.convertScalarExpression(e.ThirdExpression),
Not: e.TernaryExpressionType == "NotBetween",
Location: c.loc(e),
}
case *tsql.BooleanInExpression:
in := &ast.In{
Expr: c.convertScalarExpression(e.Expression),
Not: e.NotDefined,
Location: c.loc(e),
}
for _, item := range e.Values {
in.List = append(in.List, c.convertScalarExpression(item))
}
if e.Subquery != nil {
in.Sel = c.convertQueryExpression(e.Subquery)
}
return in
case *tsql.BooleanLikeExpression:
op := "~~"
if e.NotDefined {
op = "!~~"
}
return &ast.A_Expr{
Kind: ast.A_Expr_Kind_OP,
Name: &ast.List{
Items: []ast.Node{&ast.String{Str: op}},
},
Lexpr: c.convertScalarExpression(e.FirstExpression),
Rexpr: c.convertScalarExpression(e.SecondExpression),
Location: c.loc(e),
}
case *tsql.ExistsPredicate:
return &ast.SubLink{
SubLinkType: ast.EXISTS_SUBLINK,
Subselect: c.convertQueryExpression(e.Subquery),
Location: c.loc(e),
}
case *tsql.BooleanScalarPlaceholder:
return c.convertScalarExpression(e.Scalar)
default:
return todo(expr)
}
}
func comparisonOperator(comparisonType string) string {
switch comparisonType {
case "Equals":
return "="
case "NotEqualToBrackets", "NotEqualToExclamation":
return "<>"
case "GreaterThan":
return ">"
case "LessThan":
return "<"
case "GreaterThanOrEqualTo", "NotLessThan":
return ">="
case "LessThanOrEqualTo", "NotGreaterThan":
return "<="
default:
return "="
}
}
func (c *cc) convertScalarExpression(expr tsql.ScalarExpression) ast.Node {
if expr == nil {
return nil
}
switch e := expr.(type) {
case *tsql.ColumnReferenceExpression:
return c.convertColumnReference(e)
case *tsql.VariableReference:
return c.convertVariableReference(e)
case *tsql.IntegerLiteral:
ival, _ := strconv.ParseInt(e.Value, 10, 64)
return &ast.A_Const{
Val: &ast.Integer{Ival: ival},
Location: c.loc(e),
}
case *tsql.NumericLiteral:
return &ast.A_Const{
Val: &ast.Float{Str: e.Value},
Location: c.loc(e),
}
case *tsql.RealLiteral:
return &ast.A_Const{
Val: &ast.Float{Str: e.Value},
Location: c.loc(e),
}
case *tsql.MoneyLiteral:
return &ast.A_Const{
Val: &ast.Float{Str: e.Value},
Location: c.loc(e),
}
case *tsql.StringLiteral:
return &ast.A_Const{
Val: &ast.String{Str: e.Value},
Location: c.loc(e),
}
case *tsql.NullLiteral:
return &ast.A_Const{
Val: &ast.Null{},
Location: c.loc(e),
}
case *tsql.BinaryExpression:
return &ast.A_Expr{
Kind: ast.A_Expr_Kind_OP,
Name: &ast.List{
Items: []ast.Node{&ast.String{Str: binaryOperator(e.BinaryExpressionType)}},
},
Lexpr: c.convertScalarExpression(e.FirstExpression),
Rexpr: c.convertScalarExpression(e.SecondExpression),
Location: c.loc(e),
}
case *tsql.UnaryExpression:
switch e.UnaryExpressionType {
case "Positive":
return c.convertScalarExpression(e.Expression)
case "BitwiseNot":
return &ast.A_Expr{
Kind: ast.A_Expr_Kind_OP,
Name: &ast.List{
Items: []ast.Node{&ast.String{Str: "~"}},
},
Rexpr: c.convertScalarExpression(e.Expression),
Location: c.loc(e),
}
default: // Negative
return &ast.A_Expr{
Kind: ast.A_Expr_Kind_OP,
Name: &ast.List{
Items: []ast.Node{&ast.String{Str: "-"}},
},
Rexpr: c.convertScalarExpression(e.Expression),
Location: c.loc(e),
}
}
case *tsql.ParenthesisExpression:
return c.convertScalarExpression(e.Expression)
case *tsql.FunctionCall:
return c.convertFunctionCall(e)
case *tsql.CastCall:
return &ast.TypeCast{
Arg: c.convertScalarExpression(e.Parameter),
TypeName: dataTypeName(e.DataType),
Location: c.loc(e),
}
case *tsql.TryCastCall:
return &ast.TypeCast{
Arg: c.convertScalarExpression(e.Parameter),
TypeName: dataTypeName(e.DataType),
Location: c.loc(e),
}
case *tsql.ConvertCall:
return &ast.TypeCast{
Arg: c.convertScalarExpression(e.Parameter),
TypeName: dataTypeName(e.DataType),
Location: c.loc(e),
}
case *tsql.TryConvertCall:
return &ast.TypeCast{
Arg: c.convertScalarExpression(e.Parameter),
TypeName: dataTypeName(e.DataType),
Location: c.loc(e),
}
case *tsql.CoalesceExpression:
coalesce := &ast.CoalesceExpr{
Args: &ast.List{},
Location: c.loc(e),
}
for _, arg := range e.Expressions {
coalesce.Args.Items = append(coalesce.Args.Items, c.convertScalarExpression(arg))
}
return coalesce
case *tsql.NullIfExpression:
return &ast.FuncCall{
Funcname: &ast.List{
Items: []ast.Node{&ast.String{Str: "nullif"}},
},
Args: &ast.List{
Items: []ast.Node{
c.convertScalarExpression(e.FirstExpression),
c.convertScalarExpression(e.SecondExpression),
},
},
Location: c.loc(e),
}
case *tsql.IIfCall:
return &ast.CaseExpr{
Args: &ast.List{
Items: []ast.Node{
&ast.CaseWhen{
Expr: c.convertBooleanExpression(e.Predicate),
Result: c.convertScalarExpression(e.ThenExpression),
},
},
},
Defresult: c.convertScalarExpression(e.ElseExpression),
Location: c.loc(e),
}
case *tsql.SearchedCaseExpression:
caseExpr := &ast.CaseExpr{
Args: &ast.List{},
Location: c.loc(e),
}
for _, when := range e.WhenClauses {
caseExpr.Args.Items = append(caseExpr.Args.Items, &ast.CaseWhen{
Expr: c.convertBooleanExpression(when.WhenExpression),
Result: c.convertScalarExpression(when.ThenExpression),
})
}
if e.ElseExpression != nil {
caseExpr.Defresult = c.convertScalarExpression(e.ElseExpression)
}
return caseExpr
case *tsql.SimpleCaseExpression:
caseExpr := &ast.CaseExpr{
Arg: c.convertScalarExpression(e.InputExpression),
Args: &ast.List{},
Location: c.loc(e),
}
for _, when := range e.WhenClauses {
caseExpr.Args.Items = append(caseExpr.Args.Items, &ast.CaseWhen{
Expr: c.convertScalarExpression(when.WhenExpression),
Result: c.convertScalarExpression(when.ThenExpression),
})
}
if e.ElseExpression != nil {
caseExpr.Defresult = c.convertScalarExpression(e.ElseExpression)
}
return caseExpr
case *tsql.ScalarSubquery:
return &ast.SubLink{
SubLinkType: ast.EXPR_SUBLINK,
Subselect: c.convertQueryExpression(e.QueryExpression),
Location: c.loc(e),
}
case *tsql.ParameterlessCall:
return &ast.FuncCall{
Funcname: &ast.List{
Items: []ast.Node{&ast.String{Str: identifier(e.ParameterlessCallType)}},
},
Location: c.loc(e),
}
default:
return todo(expr)
}
}
func binaryOperator(binaryExpressionType string) string {
switch binaryExpressionType {
case "Add":
return "+"
case "Subtract":
return "-"
case "Multiply":
return "*"
case "Divide":
return "/"
case "Modulo":
return "%"
case "BitwiseAnd":
return "&"
case "BitwiseOr":
return "|"
case "BitwiseXor":
return "^"
default:
return "+"
}
}
func (c *cc) convertColumnReference(n *tsql.ColumnReferenceExpression) *ast.ColumnRef {
fields := &ast.List{}
if n.MultiPartIdentifier != nil {
for _, id := range n.MultiPartIdentifier.Identifiers {
fields.Items = append(fields.Items, NewIdentifier(id.Value))
}
}
if n.ColumnType == "Wildcard" {
fields.Items = append(fields.Items, &ast.A_Star{})
}
return &ast.ColumnRef{
Fields: fields,
Location: c.loc(n),
}
}
// convertVariableReference converts an "@name" query parameter. Repeated uses
// of a name share a single parameter number.
func (c *cc) convertVariableReference(n *tsql.VariableReference) ast.Node {
name := strings.TrimPrefix(n.Name, "@")
number, ok := c.namedParams[name]
if !ok {
c.paramCount++
number = c.paramCount
if c.namedParams == nil {
c.namedParams = map[string]int{}
}
c.namedParams[name] = number
}
return &ast.ParamRef{
Number: number,
Location: c.loc(n),
}
}
func (c *cc) convertFunctionCall(n *tsql.FunctionCall) *ast.FuncCall {
fc := &ast.FuncCall{
Funcname: &ast.List{
Items: []ast.Node{&ast.String{Str: identifierValue(n.FunctionName)}},
},
Location: c.loc(n),
AggDistinct: n.UniqueRowFilter == "Distinct",
}
for _, param := range n.Parameters {
// COUNT(*) and friends carry the star as a wildcard column reference.
if col, ok := param.(*tsql.ColumnReferenceExpression); ok && col.ColumnType == "Wildcard" && col.MultiPartIdentifier == nil {
fc.AggStar = true
continue
}
if fc.Args == nil {
fc.Args = &ast.List{}
}
fc.Args.Items = append(fc.Args.Items, c.convertScalarExpression(param))
}
if n.OverClause != nil {
fc.Over = &ast.WindowDef{Location: c.loc(n.OverClause)}
if len(n.OverClause.Partitions) > 0 {
fc.Over.PartitionClause = &ast.List{}
for _, p := range n.OverClause.Partitions {
fc.Over.PartitionClause.Items = append(fc.Over.PartitionClause.Items, c.convertScalarExpression(p))
}
}
if n.OverClause.OrderByClause != nil {
fc.Over.OrderClause = c.convertOrderByClause(n.OverClause.OrderByClause)
}
}
return fc
}
// convertOutputClause converts an OUTPUT clause to a returning list. The
// INSERTED and DELETED pseudo-table qualifiers are stripped so the columns
// resolve against the statement's target table.
func (c *cc) convertOutputClause(n *tsql.OutputClause) *ast.List {
if n == nil || len(n.SelectColumns) == 0 {
return nil
}
list := &ast.List{}
for _, elem := range n.SelectColumns {
target := c.convertSelectElement(elem)
if res, ok := target.(*ast.ResTarget); ok {
stripOutputQualifier(res.Val)
}
list.Items = append(list.Items, target)
}
return list
}
func stripOutputQualifier(node ast.Node) {
ref, ok := node.(*ast.ColumnRef)
if !ok || ref.Fields == nil || len(ref.Fields.Items) < 2 {
return
}
if first, ok := ref.Fields.Items[0].(*ast.String); ok {
switch first.Str {
case "inserted", "deleted":
ref.Fields.Items = ref.Fields.Items[1:]
}
}
}
func (c *cc) convertInsertStatement(n *tsql.InsertStatement) ast.Node {
if n.InsertSpecification == nil {
return todo(n)
}
spec := n.InsertSpecification
target, ok := spec.Target.(*tsql.NamedTableReference)
if !ok {
return todo(n)
}
stmt := &ast.InsertStmt{
Relation: c.parseRangeVar(target.SchemaObject),
}
if len(spec.Columns) > 0 {
stmt.Cols = &ast.List{}
for _, col := range spec.Columns {
if col.MultiPartIdentifier == nil || len(col.MultiPartIdentifier.Identifiers) == 0 {
continue
}
ids := col.MultiPartIdentifier.Identifiers
name := identifierValue(ids[len(ids)-1])
stmt.Cols.Items = append(stmt.Cols.Items, &ast.ResTarget{
Name: &name,
Location: c.loc(col),
})
}
}
switch src := spec.InsertSource.(type) {
case *tsql.ValuesInsertSource:
if !src.IsDefaultValues {
sel := &ast.SelectStmt{
ValuesLists: &ast.List{},
}
for _, row := range src.RowValues {
rowList := &ast.List{}
for _, val := range row.ColumnValues {
rowList.Items = append(rowList.Items, c.convertScalarExpression(val))
}
sel.ValuesLists.Items = append(sel.ValuesLists.Items, rowList)
}
stmt.SelectStmt = sel
}
case *tsql.SelectInsertSource:
stmt.SelectStmt = c.convertQueryExpression(src.Select)
}
if returning := c.convertOutputClause(spec.OutputClause); returning != nil {
stmt.ReturningList = returning
}
return stmt
}
func (c *cc) convertUpdateStatement(n *tsql.UpdateStatement) ast.Node {
if n.UpdateSpecification == nil {
return todo(n)
}
spec := n.UpdateSpecification
target, ok := spec.Target.(*tsql.NamedTableReference)
if !ok {
return todo(n)
}
rv, fromItems, quals := c.dmlTargetAndFrom(target, spec.FromClause)
stmt := &ast.UpdateStmt{
Relations: &ast.List{
Items: []ast.Node{rv},
},
TargetList: &ast.List{},
FromClause: &ast.List{Items: fromItems},
ReturningList: &ast.List{},
WithClause: c.convertWithClause(n.WithCtesAndXmlNamespaces),
}
for _, sc := range spec.SetClauses {
assign, ok := sc.(*tsql.AssignmentSetClause)
if !ok || assign.Column == nil {
continue
}
ids := assign.Column.MultiPartIdentifier
if ids == nil || len(ids.Identifiers) == 0 {
continue
}
name := identifierValue(ids.Identifiers[len(ids.Identifiers)-1])
stmt.TargetList.Items = append(stmt.TargetList.Items, &ast.ResTarget{
Name: &name,
Val: c.convertScalarExpression(assign.NewValue),
Location: c.loc(assign),
})
}
if spec.WhereClause != nil {
stmt.WhereClause = c.convertBooleanExpression(spec.WhereClause.SearchCondition)
}
stmt.WhereClause = andQuals(stmt.WhereClause, quals)
if returning := c.convertOutputClause(spec.OutputClause); returning != nil {
stmt.ReturningList = returning
}
return stmt
}
func (c *cc) convertDeleteStatement(n *tsql.DeleteStatement) ast.Node {
if n.DeleteSpecification == nil {
return todo(n)
}
spec := n.DeleteSpecification
target, ok := spec.Target.(*tsql.NamedTableReference)
if !ok {
return todo(n)
}
rv, fromItems, quals := c.dmlTargetAndFrom(target, spec.FromClause)
stmt := &ast.DeleteStmt{
Relations: &ast.List{
Items: []ast.Node{rv},
},
ReturningList: &ast.List{},
WithClause: c.convertWithClause(n.WithCtesAndXmlNamespaces),
}
if len(fromItems) > 0 {
stmt.FromClause = &ast.List{Items: fromItems}
}
if spec.WhereClause != nil {
stmt.WhereClause = c.convertBooleanExpression(spec.WhereClause.SearchCondition)
}
stmt.WhereClause = andQuals(stmt.WhereClause, quals)
if returning := c.convertOutputClause(spec.OutputClause); returning != nil {
stmt.ReturningList = returning
}
return stmt
}
// dmlTargetAndFrom resolves an UPDATE or DELETE statement's target against
// its FROM clause. T-SQL names the target by the alias the FROM clause gives
// it — "UPDATE a SET ... FROM authors a" — so a target matching a FROM
// relation becomes that relation, removed from the returned FROM items. The
// ON conditions of inner joins dissolved by the removal are returned as
// extra WHERE conditions.
func (c *cc) dmlTargetAndFrom(target *tsql.NamedTableReference, from *tsql.FromClause) (*ast.RangeVar, []ast.Node, []ast.Node) {
rv := c.parseRangeVar(target.SchemaObject)
if target.Alias != nil {
alias := identifierValue(target.Alias)
rv.Alias = &ast.Alias{Aliasname: &alias}
}
var items []ast.Node
if from != nil {
for _, ref := range from.TableReferences {
items = append(items, c.convertTableReference(ref))
}
}
if rv.Schemaname == nil && rv.Alias == nil && rv.Relname != nil {
for i, item := range items {
found, remaining, quals := extractTarget(item, *rv.Relname)
if found == nil {
continue
}
if remaining == nil {
items = append(items[:i], items[i+1:]...)
} else {
items[i] = remaining
}
return found, items, quals
}
}
return rv, items, nil
}
// extractTarget removes the relation named name — by alias, or by table name
// when unaliased — from a FROM item. It returns the extracted relation, what
// remains of the item (nil when the relation was the whole item), and the ON
// conditions of any inner join dissolved by the removal. Relations under an
// outer join are left alone: pulling one out would change the join's meaning.
func extractTarget(item ast.Node, name string) (*ast.RangeVar, ast.Node, []ast.Node) {
switch v := item.(type) {
case *ast.RangeVar:
if rangeVarNamed(v, name) {
return v, nil, nil
}
case *ast.JoinExpr:
if v.Jointype != ast.JoinTypeInner {
return nil, item, nil
}
if found, remaining, quals := extractTarget(v.Larg, name); found != nil {
if remaining == nil {
if v.Quals != nil {
quals = append(quals, v.Quals)
}
return found, v.Rarg, quals
}
v.Larg = remaining
return found, v, quals
}
if found, remaining, quals := extractTarget(v.Rarg, name); found != nil {
if remaining == nil {
if v.Quals != nil {
quals = append(quals, v.Quals)
}
return found, v.Larg, quals
}
v.Rarg = remaining
return found, v, quals
}
}
return nil, item, nil
}
func rangeVarNamed(rv *ast.RangeVar, name string) bool {
if rv.Alias != nil && rv.Alias.Aliasname != nil {
return *rv.Alias.Aliasname == name
}
return rv.Schemaname == nil && rv.Relname != nil && *rv.Relname == name
}
// andQuals conjoins the ON conditions of dissolved inner joins onto a WHERE
// clause; for inner joins the two forms are equivalent.
func andQuals(where ast.Node, quals []ast.Node) ast.Node {
for _, q := range quals {
if where == nil {
where = q
continue
}
where = &ast.BoolExpr{
Boolop: ast.BoolExprTypeAnd,
Args: &ast.List{Items: []ast.Node{where, q}},
}
}
return where
}
func (c *cc) convertCreateTableStatement(n *tsql.CreateTableStatement) ast.Node {
if n.SchemaObjectName == nil || n.Definition == nil {
return todo(n)
}
stmt := &ast.CreateTableStmt{
Name: parseTableName(n.SchemaObjectName),
}
// Column names in table-level PRIMARY KEY constraints are NOT NULL.
primaryKey := map[string]bool{}
for _, constraint := range n.Definition.TableConstraints {
unique, ok := constraint.(*tsql.UniqueConstraintDefinition)
if !ok || !unique.IsPrimaryKey {
continue
}
for _, col := range unique.Columns {
if col.Column == nil || col.Column.MultiPartIdentifier == nil {
continue
}
ids := col.Column.MultiPartIdentifier.Identifiers
if len(ids) == 0 {
continue
}
primaryKey[identifierValue(ids[len(ids)-1])] = true
}
}
for _, col := range n.Definition.ColumnDefinitions {
stmt.Cols = append(stmt.Cols, c.convertColumnDefinition(col, primaryKey))
}
return stmt
}
func (c *cc) convertColumnDefinition(n *tsql.ColumnDefinition, tablePrimaryKey map[string]bool) *ast.ColumnDef {
name := identifierValue(n.ColumnIdentifier)
colDef := &ast.ColumnDef{
Colname: name,
TypeName: dataTypeName(n.DataType),
Location: c.loc(n),
}
// T-SQL columns are nullable unless declared otherwise, except that a
// rowversion column is NOT NULL unless declared nullable, and sysname
// is defined as nvarchar(128) NOT NULL.
if n.Nullable != nil && !n.Nullable.Nullable {
colDef.IsNotNull = true
}
if n.Nullable == nil {
switch strings.TrimPrefix(colDef.TypeName.Name, "sys.") {
case "rowversion", "timestamp", "sysname":
colDef.IsNotNull = true
}
}
// IDENTITY columns are always NOT NULL.
if n.IdentityOptions != nil {
colDef.IsNotNull = true
}
for _, constraint := range n.Constraints {
switch cons := constraint.(type) {
case *tsql.NullableConstraintDefinition:
colDef.IsNotNull = !cons.Nullable
case *tsql.UniqueConstraintDefinition:
if cons.IsPrimaryKey {
colDef.PrimaryKey = true
colDef.IsNotNull = true
}
}
}
if tablePrimaryKey[name] {
colDef.PrimaryKey = true
colDef.IsNotNull = true
}
return colDef
}
// dataTypeName returns the lowercased base name of a column's declared type,
// e.g. "nvarchar" for NVARCHAR(100). Length and precision arguments do not
// name distinct types.
// dataTypeName is a type as the core reads it: its name, qualified by its
// schema for a user-defined type, with its parameters as type modifiers,
// MAX among them as the word it is.
func dataTypeName(ref tsql.DataTypeReference) *ast.TypeName {
switch t := ref.(type) {
case *tsql.SqlDataTypeReference:
out := &ast.TypeName{Name: identifier(t.SqlDataTypeOption)}
if t.Name != nil && t.Name.BaseIdentifier != nil {
out.Name = identifierValue(t.Name.BaseIdentifier)
}
for _, p := range t.Parameters {
switch v := p.(type) {
case *tsql.IntegerLiteral:
n, _ := strconv.ParseInt(v.Value, 10, 64)
out.Typmods = appendTypmod(out.Typmods, &ast.A_Const{Val: &ast.Integer{Ival: n}})
case *tsql.MaxLiteral:
out.Typmods = appendTypmod(out.Typmods, &ast.String{Str: "max"})
}
}
return out
case *tsql.XmlDataTypeReference:
return &ast.TypeName{Name: "xml"}
case *tsql.UserDataTypeReference:
if t.Name != nil && t.Name.BaseIdentifier != nil {
name := identifierValue(t.Name.BaseIdentifier)
if t.Name.SchemaIdentifier != nil {
name = identifierValue(t.Name.SchemaIdentifier) + "." + name
}
return &ast.TypeName{Name: name}
}
}
return &ast.TypeName{}
}
func appendTypmod(l *ast.List, n ast.Node) *ast.List {
if l == nil {
l = &ast.List{}
}
l.Items = append(l.Items, n)
return l
}
func (c *cc) convertDropTableStatement(n *tsql.DropTableStatement) ast.Node {
stmt := &ast.DropTableStmt{
IfExists: n.IsIfExists,
}
for _, obj := range n.Objects {
stmt.Tables = append(stmt.Tables, parseTableName(obj))
}
return stmt
}
func (c *cc) convertAlterTableAddTableElementStatement(n *tsql.AlterTableAddTableElementStatement) ast.Node {
if n.SchemaObjectName == nil || n.Definition == nil {
return todo(n)
}
stmt := &ast.AlterTableStmt{
Table: parseTableName(n.SchemaObjectName),
Cmds: &ast.List{},
}
for _, col := range n.Definition.ColumnDefinitions {
def := c.convertColumnDefinition(col, nil)
stmt.Cmds.Items = append(stmt.Cmds.Items, &ast.AlterTableCmd{
Name: &def.Colname,
Subtype: ast.AT_AddColumn,
Def: def,
})
}
return stmt
}
func (c *cc) convertAlterTableDropTableElementStatement(n *tsql.AlterTableDropTableElementStatement) ast.Node {
if n.SchemaObjectName == nil {
return todo(n)
}
stmt := &ast.AlterTableStmt{
Table: parseTableName(n.SchemaObjectName),
Cmds: &ast.List{},
}
for _, elem := range n.AlterTableDropTableElements {
if elem.TableElementType != "Column" || elem.Name == nil {
continue
}
name := identifierValue(elem.Name)
stmt.Cmds.Items = append(stmt.Cmds.Items, &ast.AlterTableCmd{
Name: &name,
Subtype: ast.AT_DropColumn,
MissingOk: elem.IsIfExists,
})
}
if len(stmt.Cmds.Items) == 0 {
return todo(n)
}
return stmt
}