codegen: wrap foreign key checks for nullable FKs in "if a.Val != 0 { ... }"

This commit is contained in:
2026-02-14 18:33:41 -08:00
parent d572745613
commit 17fc8a68f6

View File

@@ -161,79 +161,92 @@ func buildFKCheckLambda(tbl schema.Table) (*ast.AssignStmt, bool) {
structFieldName := col.GoFieldName() structFieldName := col.GoFieldName()
structField := &ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent(structFieldName)} structField := &ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent(structFieldName)}
if col.IsNonCodeTableForeignKey() { ret = append(ret, func() ast.Stmt {
// Real foreign key; look up referent by ID to see if it exists // Wrap nullable FKs in "if a.val != 0 { ... }"
ret = append(ret, &ast.IfStmt{ wrap := func(input ast.Stmt) ast.Stmt {
Init: &ast.AssignStmt{ if col.IsNullableForeignKey() {
Lhs: []ast.Expr{ast.NewIdent("_"), ast.NewIdent("err")}, return &ast.IfStmt{
Tok: token.DEFINE, Cond: &ast.BinaryExpr{X: structField, Op: token.NEQ, Y: &ast.BasicLit{Kind: token.INT, Value: "0"}},
Rhs: []ast.Expr{ Body: &ast.BlockStmt{List: []ast.Stmt{input}},
&ast.CallExpr{ }
Fun: &ast.SelectorExpr{X: ast.NewIdent("db"), Sel: ast.NewIdent(getByIDFuncName(col.ForeignKeyTargetTable))}, } else {
Args: []ast.Expr{structField}, return input
}
}
if col.IsNonCodeTableForeignKey() {
// Real foreign key; look up referent by ID to see if it exists
return wrap(&ast.IfStmt{
Init: &ast.AssignStmt{
Lhs: []ast.Expr{ast.NewIdent("_"), ast.NewIdent("err")},
Tok: token.DEFINE,
Rhs: []ast.Expr{
&ast.CallExpr{
Fun: &ast.SelectorExpr{X: ast.NewIdent("db"), Sel: ast.NewIdent(getByIDFuncName(col.ForeignKeyTargetTable))},
Args: []ast.Expr{structField},
},
}, },
}, },
}, Cond: &ast.CallExpr{
Cond: &ast.CallExpr{ Fun: &ast.SelectorExpr{X: ast.NewIdent("errors"), Sel: ast.NewIdent("Is")},
Fun: &ast.SelectorExpr{X: ast.NewIdent("errors"), Sel: ast.NewIdent("Is")}, Args: []ast.Expr{ast.NewIdent("err"), ast.NewIdent("ErrNotInDB")},
Args: []ast.Expr{ast.NewIdent("err"), ast.NewIdent("ErrNotInDB")}, },
}, Body: &ast.BlockStmt{
Body: &ast.BlockStmt{ List: []ast.Stmt{
List: []ast.Stmt{ &ast.ReturnStmt{
&ast.ReturnStmt{ Results: []ast.Expr{
Results: []ast.Expr{ &ast.CallExpr{
&ast.CallExpr{ Fun: ast.NewIdent("NewForeignKeyError"),
Fun: ast.NewIdent("NewForeignKeyError"), Args: []ast.Expr{
Args: []ast.Expr{ &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", structFieldName)},
&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", structFieldName)}, &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", col.ForeignKeyTargetTable)},
&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", col.ForeignKeyTargetTable)}, structField,
structField, },
}, },
}, },
}, },
}, },
}, },
}, })
}) } else {
} else { // Code table value. Query the table to see if it exists
// Code table value. Query the table to see if it exists return wrap(&ast.IfStmt{
ret = append(ret, &ast.IfStmt{ Init: &ast.AssignStmt{
Init: &ast.AssignStmt{ Lhs: []ast.Expr{ast.NewIdent("err")},
Lhs: []ast.Expr{ast.NewIdent("err")}, Tok: token.DEFINE,
Tok: token.DEFINE, Rhs: []ast.Expr{
Rhs: []ast.Expr{ &ast.CallExpr{
&ast.CallExpr{ Fun: &ast.SelectorExpr{X: dbDB, Sel: ast.NewIdent("Get")},
Fun: &ast.SelectorExpr{X: dbDB, Sel: ast.NewIdent("Get")}, Args: []ast.Expr{
Args: []ast.Expr{ &ast.CallExpr{Fun: ast.NewIdent("new"), Args: []ast.Expr{ast.NewIdent("int")}},
&ast.CallExpr{Fun: ast.NewIdent("new"), Args: []ast.Expr{ast.NewIdent("int")}}, &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("`select 1 from %s where rowid = ?`", col.ForeignKeyTargetTable)},
&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("`select 1 from %s where rowid = ?`", col.ForeignKeyTargetTable)}, structField,
structField, },
}, },
}, },
}, },
}, Cond: &ast.CallExpr{
Cond: &ast.CallExpr{ Fun: &ast.SelectorExpr{X: ast.NewIdent("errors"), Sel: ast.NewIdent("Is")},
Fun: &ast.SelectorExpr{X: ast.NewIdent("errors"), Sel: ast.NewIdent("Is")}, Args: []ast.Expr{ast.NewIdent("err"), &ast.SelectorExpr{X: ast.NewIdent("sql"), Sel: ast.NewIdent("ErrNoRows")}},
Args: []ast.Expr{ast.NewIdent("err"), &ast.SelectorExpr{X: ast.NewIdent("sql"), Sel: ast.NewIdent("ErrNoRows")}}, },
}, Body: &ast.BlockStmt{
Body: &ast.BlockStmt{ List: []ast.Stmt{
List: []ast.Stmt{ &ast.ReturnStmt{
&ast.ReturnStmt{ Results: []ast.Expr{
Results: []ast.Expr{ &ast.CallExpr{
&ast.CallExpr{ Fun: ast.NewIdent("NewForeignKeyError"),
Fun: ast.NewIdent("NewForeignKeyError"), Args: []ast.Expr{
Args: []ast.Expr{ &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", structFieldName)},
&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", structFieldName)}, &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", col.ForeignKeyTargetTable)},
&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", col.ForeignKeyTargetTable)}, structField,
structField, },
}, },
}, },
}, },
}, },
}, },
}, })
}) }
} }())
} }
// final return nil // final return nil
ret = append(ret, &ast.ReturnStmt{Results: []ast.Expr{ast.NewIdent("nil")}}) ret = append(ret, &ast.ReturnStmt{Results: []ast.Expr{ast.NewIdent("nil")}})