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
170 lines
4.5 KiB
Go
170 lines
4.5 KiB
Go
package analyzer
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/sqlc-dev/sqlc/internal/core"
|
|
"github.com/sqlc-dev/sqlc/internal/sql/ast"
|
|
)
|
|
|
|
func (a *analyzer) projectTarget(rt *ast.ResTarget) error {
|
|
// A column reference's field list is flattened once here and threaded
|
|
// through the star check, the star expansion and the output name.
|
|
var fields []string
|
|
if cr, ok := rt.Val.(*ast.ColumnRef); ok {
|
|
fields = flattenFields(cr.Fields)
|
|
if isStar(fields) {
|
|
a.emitStar(rt, fields)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
t, err := a.typeExpr(rt.Val)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// A placeholder selected directly is named by its alias and, when nothing
|
|
// constrains it, typed as the dialect types such a placeholder.
|
|
if pr, ok := rt.Val.(*ast.ParamRef); ok {
|
|
if rt.Name != nil && *rt.Name != "" {
|
|
if p := a.params[pr.Number]; p.Name == "" {
|
|
p.Name = *rt.Name
|
|
a.params[pr.Number] = p
|
|
}
|
|
}
|
|
if t.typeOID == 0 && t.expr == nil {
|
|
if oid, ok := a.cat.UntypedTypeOID(); ok {
|
|
t = exprType{typeOID: oid, nullable: true}
|
|
}
|
|
}
|
|
}
|
|
col := core.Column{
|
|
Name: targetName(rt, fields),
|
|
TypeOID: t.typeOID,
|
|
NotNull: !t.nullable,
|
|
SourceClassOID: t.sourceClassOID,
|
|
SourceAttributeOID: t.sourceAttributeOID,
|
|
}
|
|
col.DataType, col.IsArray = a.typeNameOf(t)
|
|
col.Type = a.typeExprOf(t)
|
|
a.decorateSource(&col, t.sourceAttributeOID, t.sourceTableAlias)
|
|
if rt.Name == nil || *rt.Name == "" {
|
|
// A column whose own name is dotted, as ClickHouse's n.a is, is
|
|
// reported under that name rather than its last part.
|
|
if col.Source != nil && strings.Contains(col.Source.Column, ".") {
|
|
col.Name = col.Source.Column
|
|
}
|
|
a.qualifyDuplicate(&col, t.sourceTableAlias)
|
|
}
|
|
a.columns = append(a.columns, col)
|
|
return nil
|
|
}
|
|
|
|
// qualifyDuplicate names a column after its relation when an earlier result
|
|
// column from another relation already has its name, in a dialect that
|
|
// does so.
|
|
func (a *analyzer) qualifyDuplicate(col *core.Column, alias string) {
|
|
if alias == "" || !a.cat.QualifiesDuplicateColumns() {
|
|
return
|
|
}
|
|
for _, prev := range a.columns {
|
|
if prev.Name != col.Name {
|
|
continue
|
|
}
|
|
prevAlias := ""
|
|
if prev.Source != nil {
|
|
prevAlias = prev.Source.TableAlias
|
|
}
|
|
if prevAlias != alias {
|
|
col.Name = alias + "." + col.Name
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *analyzer) decorateSource(col *core.Column, attOID int64, tableAlias string) {
|
|
if attOID == 0 {
|
|
return
|
|
}
|
|
ad, err := a.cat.LookupAttribute(attOID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
col.Source = &core.ColumnSource{
|
|
Schema: ad.Schema,
|
|
Table: ad.Table,
|
|
TableAlias: tableAlias,
|
|
Column: ad.Column,
|
|
}
|
|
col.DeclType = ad.DeclType
|
|
col.IsPrimaryKey = ad.IsPrimaryKey
|
|
col.IsUnique = ad.IsUnique
|
|
col.IsAutoIncrement = ad.AutoIncrement
|
|
}
|
|
|
|
// targetName picks the output name for a target. fields is the already
|
|
// flattened field list when rt.Val is a column reference, and nil otherwise.
|
|
func targetName(rt *ast.ResTarget, fields []string) string {
|
|
if rt.Name != nil && *rt.Name != "" {
|
|
return *rt.Name
|
|
}
|
|
if len(fields) > 0 {
|
|
return fields[len(fields)-1]
|
|
}
|
|
if fc, ok := rt.Val.(*ast.FuncCall); ok {
|
|
if name := funcCallName(fc); name != "" {
|
|
return name
|
|
}
|
|
}
|
|
return "?column?"
|
|
}
|
|
|
|
func isStar(fields []string) bool {
|
|
return len(fields) > 0 && fields[len(fields)-1] == "*"
|
|
}
|
|
|
|
func (a *analyzer) emitStar(rt *ast.ResTarget, fields []string) {
|
|
relName := ""
|
|
if len(fields) > 1 {
|
|
relName = fields[0]
|
|
}
|
|
// The star is reported along with the columns it covers, so the query text
|
|
// can be rewritten to name them.
|
|
star := core.StarExpansion{Location: rt.Location, Fields: fields}
|
|
if rt.Name != nil {
|
|
star.Alias = *rt.Name
|
|
}
|
|
for _, rel := range a.scope.rels {
|
|
if relName != "" && rel.alias != relName {
|
|
continue
|
|
}
|
|
a.columns = slices.Grow(a.columns, len(rel.cols))
|
|
star.Columns = slices.Grow(star.Columns, len(rel.cols))
|
|
for _, c := range rel.cols {
|
|
if c.Hidden {
|
|
continue
|
|
}
|
|
col := core.Column{
|
|
Name: c.Name,
|
|
TypeOID: c.TypeOID,
|
|
NotNull: c.NotNull,
|
|
SourceClassOID: rel.classOID,
|
|
SourceAttributeOID: c.AttOID,
|
|
}
|
|
t := exprType{typeOID: c.TypeOID, expr: c.Type, nullable: !c.NotNull}
|
|
col.DataType, col.IsArray = a.typeNameOf(t)
|
|
col.Type = a.typeExprOf(t)
|
|
a.decorateSource(&col, c.AttOID, rel.alias)
|
|
a.qualifyDuplicate(&col, rel.alias)
|
|
a.columns = append(a.columns, col)
|
|
star.Columns = append(star.Columns, core.StarColumn{
|
|
Relation: rel.alias,
|
|
Name: c.Name,
|
|
DataType: col.DataType,
|
|
})
|
|
}
|
|
}
|
|
a.recordStar(star)
|
|
}
|