Files
upstream-sqlc/internal/compiler/returning.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

139 lines
3.6 KiB
Go

package compiler
import (
"github.com/sqlc-dev/sqlc/internal/config"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
)
// returningTables builds virtual tables for the OLD and NEW aliases that
// PostgreSQL 18 makes available in the RETURNING clause of INSERT, UPDATE and
// DELETE statements. Each alias exposes the columns of the statement's target
// table. For INSERT there is usually no old row and for DELETE there is no
// new row, so every column reached through those aliases becomes nullable.
func (c *Compiler) returningTables(qc *QueryCatalog, node ast.Node) ([]*Table, error) {
if c.conf.Engine != config.EnginePostgreSQL {
return nil, nil
}
var rv *ast.RangeVar
var returning *ast.List
oldAlias, newAlias := "old", "new"
var oldNullable, newNullable bool
switch n := node.(type) {
case *ast.DeleteStmt:
rv = firstRangeVar(n.Relations)
returning = n.ReturningList
if n.ReturningOldAlias != "" {
oldAlias = n.ReturningOldAlias
}
if n.ReturningNewAlias != "" {
newAlias = n.ReturningNewAlias
}
// A deleted row has no new value
newNullable = true
case *ast.InsertStmt:
rv = n.Relation
returning = n.ReturningList
if n.ReturningOldAlias != "" {
oldAlias = n.ReturningOldAlias
}
if n.ReturningNewAlias != "" {
newAlias = n.ReturningNewAlias
}
// An inserted row has no old value, except when an ON CONFLICT
// clause updates an existing row instead
oldNullable = true
case *ast.UpdateStmt:
rv = firstRangeVar(n.Relations)
returning = n.ReturningList
if n.ReturningOldAlias != "" {
oldAlias = n.ReturningOldAlias
}
if n.ReturningNewAlias != "" {
newAlias = n.ReturningNewAlias
}
default:
return nil, nil
}
if rv == nil || returning == nil || len(returning.Items) == 0 {
return nil, nil
}
fqn, err := ParseTableName(rv)
if err != nil {
return nil, err
}
build := func(alias string, nullable bool) *Table {
table, err := qc.GetTable(fqn)
if err != nil {
// An unresolvable target table is reported by the regular
// source table lookup, so ignore the error here
return nil
}
table.Rel = &ast.TableName{Name: alias}
if nullable {
for _, col := range table.Columns {
col.NotNull = false
}
}
return table
}
var tables []*Table
if t := build(oldAlias, oldNullable); t != nil {
tables = append(tables, t)
}
if t := build(newAlias, newNullable); t != nil {
tables = append(tables, t)
}
return tables, nil
}
func firstRangeVar(list *ast.List) *ast.RangeVar {
if list == nil {
return nil
}
for _, item := range list.Items {
if rv, ok := item.(*ast.RangeVar); ok && rv != nil {
return rv
}
}
return nil
}
// returningTableForScope returns the OLD or NEW virtual table named by scope.
// A source table with the same name shadows the virtual table, matching
// PostgreSQL, where the implicit aliases are only available when no relation
// in the query is already known under that name.
func returningTableForScope(tables, rtables []*Table, scope string) *Table {
if scope == "" {
return nil
}
for _, t := range tables {
if t.Rel.Name == scope {
return nil
}
}
for _, t := range rtables {
if t.Rel.Name == scope {
return t
}
}
return nil
}
// tablesForRef resolves a column reference against the source tables,
// extended with the OLD or NEW virtual table when the reference is qualified
// with one of their names.
func tablesForRef(ref *ast.ColumnRef, tables, rtables []*Table) []*Table {
parts := stringSlice(ref.Fields)
if len(parts) != 2 {
return tables
}
if vt := returningTableForScope(tables, rtables, parts[0]); vt != nil {
return append(append([]*Table{}, tables...), vt)
}
return tables
}