Files
upstream-sqlc/internal/core/analyzer/projection.go
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

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)
}