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
1200 lines
32 KiB
Go
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
|
|
}
|