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
139 lines
3.6 KiB
Go
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
|
|
}
|