snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
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
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
This commit is contained in:
211
internal/core/analyzer/dml.go
Normal file
211
internal/core/analyzer/dml.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/core"
|
||||
"github.com/sqlc-dev/sqlc/internal/sql/ast"
|
||||
)
|
||||
|
||||
func (a *analyzer) analyzeInsert(s *ast.InsertStmt) error {
|
||||
if s.Relation == nil {
|
||||
return fmt.Errorf("insert: missing relation")
|
||||
}
|
||||
rel, err := a.bindRangeVar(s.Relation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.scope = &scope{rels: []scopeRel{rel}}
|
||||
|
||||
targets, err := insertTargets(rel, s.Cols)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.bindInsertValues(s.SelectStmt, rel, targets); err != nil {
|
||||
return err
|
||||
}
|
||||
return a.projectReturning(s.ReturningList)
|
||||
}
|
||||
|
||||
func (a *analyzer) analyzeUpdate(s *ast.UpdateStmt) error {
|
||||
if err := a.bindCTEs(s.WithClause); err != nil {
|
||||
return err
|
||||
}
|
||||
sc, err := a.relationScope(s.Relations, s.FromClause, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.scope = sc
|
||||
target := sc.rels[0]
|
||||
|
||||
for _, item := range listItems(s.TargetList) {
|
||||
rt, ok := item.(*ast.ResTarget)
|
||||
if !ok || rt.Name == nil {
|
||||
continue
|
||||
}
|
||||
col, ok := findColumn(target, *rt.Name)
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown column %q", *rt.Name)
|
||||
}
|
||||
if err := a.bindValue(target, &col, rt.Val); err != nil {
|
||||
return fmt.Errorf("set %s: %w", *rt.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if s.WhereClause != nil {
|
||||
if _, err := a.typeExpr(s.WhereClause); err != nil {
|
||||
return fmt.Errorf("where: %w", err)
|
||||
}
|
||||
}
|
||||
return a.projectReturning(s.ReturningList)
|
||||
}
|
||||
|
||||
func (a *analyzer) analyzeDelete(s *ast.DeleteStmt) error {
|
||||
if err := a.bindCTEs(s.WithClause); err != nil {
|
||||
return err
|
||||
}
|
||||
sc, err := a.relationScope(s.Relations, s.UsingClause, s.FromClause)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.scope = sc
|
||||
|
||||
if s.WhereClause != nil {
|
||||
if _, err := a.typeExpr(s.WhereClause); err != nil {
|
||||
return fmt.Errorf("where: %w", err)
|
||||
}
|
||||
}
|
||||
return a.projectReturning(s.ReturningList)
|
||||
}
|
||||
|
||||
// relationScope builds the scope a DML statement operates on: the relations it
|
||||
// targets, whatever a USING or FROM clause joins in, and — for the engines that
|
||||
// report a multi-table DELETE that way — a single FROM node.
|
||||
func (a *analyzer) relationScope(relations, extra *ast.List, from ast.Node) (*scope, error) {
|
||||
sc := &scope{}
|
||||
defer a.binding(sc)()
|
||||
for _, item := range listItems(relations) {
|
||||
if err := a.appendFromItem(sc, item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, item := range listItems(extra) {
|
||||
if err := a.appendFromItem(sc, item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if from != nil {
|
||||
if err := a.appendFromItem(sc, from); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(sc.rels) == 0 {
|
||||
return nil, fmt.Errorf("missing target relation")
|
||||
}
|
||||
return sc, nil
|
||||
}
|
||||
|
||||
func insertTargets(rel scopeRel, cols *ast.List) ([]core.ClassColumn, error) {
|
||||
items := listItems(cols)
|
||||
if len(items) == 0 {
|
||||
out := make([]core.ClassColumn, 0, len(rel.cols))
|
||||
for _, col := range rel.cols {
|
||||
if !col.Hidden {
|
||||
out = append(out, col)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
out := make([]core.ClassColumn, 0, len(items))
|
||||
for _, item := range items {
|
||||
rt, ok := item.(*ast.ResTarget)
|
||||
if !ok || rt.Name == nil {
|
||||
return nil, fmt.Errorf("insert: unsupported column target %T", item)
|
||||
}
|
||||
col, ok := findColumn(rel, *rt.Name)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown column %q", *rt.Name)
|
||||
}
|
||||
out = append(out, col)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (a *analyzer) bindInsertValues(n ast.Node, rel scopeRel, targets []core.ClassColumn) error {
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
sel, ok := n.(*ast.SelectStmt)
|
||||
if !ok {
|
||||
return fmt.Errorf("insert: unsupported source %T", n)
|
||||
}
|
||||
// INSERT ... SELECT inserts whatever the query returns. The rows are not
|
||||
// the statement's result, but the query still holds placeholders.
|
||||
if sel.ValuesLists == nil {
|
||||
_, err := a.subqueryColumns(sel)
|
||||
return err
|
||||
}
|
||||
for _, row := range listItems(sel.ValuesLists) {
|
||||
values, ok := row.(*ast.List)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for i, v := range values.Items {
|
||||
var target *core.ClassColumn
|
||||
if i < len(targets) {
|
||||
target = &targets[i]
|
||||
}
|
||||
if err := a.bindValue(rel, target, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *analyzer) bindValue(rel scopeRel, target *core.ClassColumn, v ast.Node) error {
|
||||
if target != nil {
|
||||
switch value := v.(type) {
|
||||
case *ast.ParamRef:
|
||||
a.inferParam(value.Number, columnType(rel, *target))
|
||||
return nil
|
||||
case *ast.A_Const:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
_, err := a.typeExpr(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *analyzer) projectReturning(l *ast.List) error {
|
||||
for _, item := range listItems(l) {
|
||||
rt, ok := item.(*ast.ResTarget)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if err := a.projectTarget(rt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findColumn(rel scopeRel, name string) (core.ClassColumn, bool) {
|
||||
for _, col := range rel.cols {
|
||||
if col.Name == name {
|
||||
return col, true
|
||||
}
|
||||
}
|
||||
return core.ClassColumn{}, false
|
||||
}
|
||||
|
||||
func columnType(rel scopeRel, col core.ClassColumn) exprType {
|
||||
return exprType{
|
||||
typeOID: col.TypeOID,
|
||||
expr: col.Type,
|
||||
nullable: !col.NotNull,
|
||||
sourceClassOID: rel.classOID,
|
||||
sourceAttributeOID: col.AttOID,
|
||||
sourceTableAlias: rel.alias,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user