From 17fc8a68f68cd459cef0ef5da61f7f45699cf087 Mon Sep 17 00:00:00 2001 From: ~wispem-wantex Date: Sat, 14 Feb 2026 18:33:41 -0800 Subject: [PATCH] codegen: wrap foreign key checks for nullable FKs in "if a.Val != 0 { ... }" --- pkg/codegen/modelgenerate/generate_model.go | 129 +++++++++++--------- 1 file changed, 71 insertions(+), 58 deletions(-) diff --git a/pkg/codegen/modelgenerate/generate_model.go b/pkg/codegen/modelgenerate/generate_model.go index 37acb5f..1bad844 100644 --- a/pkg/codegen/modelgenerate/generate_model.go +++ b/pkg/codegen/modelgenerate/generate_model.go @@ -161,79 +161,92 @@ func buildFKCheckLambda(tbl schema.Table) (*ast.AssignStmt, bool) { structFieldName := col.GoFieldName() structField := &ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent(structFieldName)} - if col.IsNonCodeTableForeignKey() { - // Real foreign key; look up referent by ID to see if it exists - ret = append(ret, &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}, + ret = append(ret, func() ast.Stmt { + // Wrap nullable FKs in "if a.val != 0 { ... }" + wrap := func(input ast.Stmt) ast.Stmt { + if col.IsNullableForeignKey() { + return &ast.IfStmt{ + Cond: &ast.BinaryExpr{X: structField, Op: token.NEQ, Y: &ast.BasicLit{Kind: token.INT, Value: "0"}}, + Body: &ast.BlockStmt{List: []ast.Stmt{input}}, + } + } else { + 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{ - Fun: &ast.SelectorExpr{X: ast.NewIdent("errors"), Sel: ast.NewIdent("Is")}, - Args: []ast.Expr{ast.NewIdent("err"), ast.NewIdent("ErrNotInDB")}, - }, - Body: &ast.BlockStmt{ - List: []ast.Stmt{ - &ast.ReturnStmt{ - Results: []ast.Expr{ - &ast.CallExpr{ - Fun: ast.NewIdent("NewForeignKeyError"), - Args: []ast.Expr{ - &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", structFieldName)}, - &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", col.ForeignKeyTargetTable)}, - structField, + Cond: &ast.CallExpr{ + Fun: &ast.SelectorExpr{X: ast.NewIdent("errors"), Sel: ast.NewIdent("Is")}, + Args: []ast.Expr{ast.NewIdent("err"), ast.NewIdent("ErrNotInDB")}, + }, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.ReturnStmt{ + Results: []ast.Expr{ + &ast.CallExpr{ + Fun: ast.NewIdent("NewForeignKeyError"), + Args: []ast.Expr{ + &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", structFieldName)}, + &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", col.ForeignKeyTargetTable)}, + structField, + }, }, }, }, }, }, - }, - }) - } else { - // Code table value. Query the table to see if it exists - ret = append(ret, &ast.IfStmt{ - Init: &ast.AssignStmt{ - Lhs: []ast.Expr{ast.NewIdent("err")}, - Tok: token.DEFINE, - Rhs: []ast.Expr{ - &ast.CallExpr{ - Fun: &ast.SelectorExpr{X: dbDB, Sel: ast.NewIdent("Get")}, - Args: []ast.Expr{ - &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)}, - structField, + }) + } else { + // Code table value. Query the table to see if it exists + return wrap(&ast.IfStmt{ + Init: &ast.AssignStmt{ + Lhs: []ast.Expr{ast.NewIdent("err")}, + Tok: token.DEFINE, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.SelectorExpr{X: dbDB, Sel: ast.NewIdent("Get")}, + Args: []ast.Expr{ + &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)}, + structField, + }, }, }, }, - }, - Cond: &ast.CallExpr{ - 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")}}, - }, - Body: &ast.BlockStmt{ - List: []ast.Stmt{ - &ast.ReturnStmt{ - Results: []ast.Expr{ - &ast.CallExpr{ - Fun: ast.NewIdent("NewForeignKeyError"), - Args: []ast.Expr{ - &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", structFieldName)}, - &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", col.ForeignKeyTargetTable)}, - structField, + Cond: &ast.CallExpr{ + 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")}}, + }, + Body: &ast.BlockStmt{ + List: []ast.Stmt{ + &ast.ReturnStmt{ + Results: []ast.Expr{ + &ast.CallExpr{ + Fun: ast.NewIdent("NewForeignKeyError"), + Args: []ast.Expr{ + &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", structFieldName)}, + &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", col.ForeignKeyTargetTable)}, + structField, + }, }, }, }, }, }, - }, - }) - } + }) + } + }()) } // final return nil ret = append(ret, &ast.ReturnStmt{Results: []ast.Expr{ast.NewIdent("nil")}})