codegen: CreateUpdateDelete test no longer creates an "Update" block if there's no updateable fields

This commit is contained in:
2026-09-19 21:36:31 -07:00
parent f98f8416e8
commit 6cf0b66dd5
4 changed files with 99 additions and 57 deletions

View File

@@ -296,9 +296,8 @@ func GenerateSaveItemFunc(tbl schema.Table) *ast.FuncDecl {
updatePairs := make([]string, 0, len(tbl.Columns)) updatePairs := make([]string, 0, len(tbl.Columns))
hasCreatedAt, hasUpdatedAt := tbl.HasAutoTimestamps() hasCreatedAt, hasUpdatedAt := tbl.HasAutoTimestamps()
hasSoftDelete := tbl.HasSoftDelete()
// Assemble data for building SQL "insert" and "update" strings // Assemble SQL "insert" string
for _, col := range tbl.Columns { for _, col := range tbl.Columns {
if col.Name == "rowid" { if col.Name == "rowid" {
continue continue
@@ -309,23 +308,20 @@ func GenerateSaveItemFunc(tbl schema.Table) *ast.FuncDecl {
val = fmt.Sprintf("nullif(%s, 0)", val) val = fmt.Sprintf("nullif(%s, 0)", val)
} }
insertVals = append(insertVals, val) insertVals = append(insertVals, val)
// created_at should not be updated after creation
if col.Name == "created_at" && hasCreatedAt {
continue
}
// Soft-delete state is managed by SoftDeleteXyz(), not by updates
if hasSoftDelete && (col.Name == "is_deleted" || col.Name == "deleted_at") {
continue
}
if !col.IsPrimaryKey { // Don't try to update primary key columns (mainly for w/o rowid tables)
updatePairs = append(updatePairs, col.Name+"="+val)
}
} }
insertStmt := fmt.Sprintf("\n\t\t insert into %s (%s)\n\t\t values (%s)\n\t\t", insertStmt := fmt.Sprintf("\n\t\t insert into %s (%s)\n\t\t values (%s)\n\t\t",
tbl.TableName, tbl.TableName,
strings.Join(insertCols, ", "), strings.Join(insertCols, ", "),
strings.Join(insertVals, ", "), strings.Join(insertVals, ", "),
) )
// ...and "update" string
for _, col := range tbl.GetUpdateableColumns() {
val := ":" + col.Name
if col.IsNullableForeignKey() {
val = fmt.Sprintf("nullif(%s, 0)", val)
}
updatePairs = append(updatePairs, col.Name+"="+val)
}
updateStmt := fmt.Sprintf("\n\t\t update %s\n\t\t set %s\n\t\t where rowid = :rowid\n\t\t", updateStmt := fmt.Sprintf("\n\t\t update %s\n\t\t set %s\n\t\t where rowid = :rowid\n\t\t",
tbl.TableName, tbl.TableName,
strings.Join(updatePairs, ",\n\t\t "), strings.Join(updatePairs, ",\n\t\t "),

View File

@@ -67,26 +67,18 @@ func SampleValue(c pkgschema.Column, offset int) ast.Expr {
} }
// UpdateTestFields returns the columns for which the test generator should assign its own // UpdateTestFields returns the columns for which the test generator should assign its own
// sample values, in the MakeXyz() factory and in the create/update test: the same columns // sample values in MakeXyz() and the create/update test. It starts with Table.GetUpdateableColumns()
// that GenerateSaveItemFunc's "update" branch writes to, minus foreign keys (which can't be // and removes fields the generated test should not assign directly: foreign keys and auto-managed
// given plausible values here), the auto-managed "created_at"/"updated_at" columns, and the // updated_at.
// soft-delete columns (which SoftDeleteXyz() manages).
func UpdateTestFields(tbl pkgschema.Table) (ret []pkgschema.Column) { func UpdateTestFields(tbl pkgschema.Table) (ret []pkgschema.Column) {
hasCreatedAt, hasUpdatedAt := tbl.HasAutoTimestamps() _, hasUpdatedAt := tbl.HasAutoTimestamps()
hasSoftDelete := tbl.HasSoftDelete() for _, c := range tbl.GetUpdateableColumns() {
for _, c := range tbl.Columns { if c.IsForeignKey {
if c.Name == "rowid" || c.IsPrimaryKey || c.IsForeignKey {
continue
}
if c.Name == "created_at" && hasCreatedAt {
continue continue
} }
if c.Name == "updated_at" && hasUpdatedAt { if c.Name == "updated_at" && hasUpdatedAt {
continue continue
} }
if hasSoftDelete && (c.Name == "is_deleted" || c.Name == "deleted_at") {
continue
}
ret = append(ret, c) ret = append(ret, c)
} }
return return
@@ -486,39 +478,41 @@ func GenerateModelTestAST(tbl pkgschema.Table, schema pkgschema.Schema, gomodNam
makeDeepEqual(testObj, testObj2), makeDeepEqual(testObj, testObj2),
) )
stmts = append(stmts, if len(tbl.GetUpdateableColumns()) > 0 { // Skip if no mutable fields
BlankLine(), stmts = append(stmts,
Comment("Update"), BlankLine(),
) Comment("Update"),
)
// item.Description = <sample value, offset 2> // item.Description = <sample value, offset 2>
// ...one assignment per updatable field // ...one assignment per updatable field
for _, c := range updateTestFields { for _, c := range updateTestFields {
stmts = append(stmts, &ast.AssignStmt{ stmts = append(stmts, &ast.AssignStmt{
Lhs: []ast.Expr{&ast.SelectorExpr{X: testObj, Sel: ast.NewIdent(c.GoFieldName())}}, Lhs: []ast.Expr{&ast.SelectorExpr{X: testObj, Sel: ast.NewIdent(c.GoFieldName())}},
Tok: token.ASSIGN, Tok: token.ASSIGN,
Rhs: []ast.Expr{SampleValue(c, 2)}, Rhs: []ast.Expr{SampleValue(c, 2)},
}) })
}
stmts = append(stmts,
// TestDB.SaveItem(&item)
&ast.ExprStmt{X: &ast.CallExpr{
Fun: &ast.SelectorExpr{X: testDB, Sel: ast.NewIdent("Save" + tbl.GoTypeName)},
Args: []ast.Expr{&ast.UnaryExpr{Op: token.AND, X: testObj}},
}},
// item2 = must.Get(TestDB.GetItemByID(item.ID))
&ast.AssignStmt{
Lhs: []ast.Expr{testObj2},
Tok: token.ASSIGN,
Rhs: []ast.Expr{mustCall(getItemByPKCall(testObj))},
},
// if deep.Equal(...) {...}
makeDeepEqual(testObj, testObj2),
)
} }
stmts = append(stmts,
// TestDB.SaveItem(&item)
&ast.ExprStmt{X: &ast.CallExpr{
Fun: &ast.SelectorExpr{X: testDB, Sel: ast.NewIdent("Save" + tbl.GoTypeName)},
Args: []ast.Expr{&ast.UnaryExpr{Op: token.AND, X: testObj}},
}},
// item2 = must.Get(TestDB.GetItemByID(item.ID))
&ast.AssignStmt{
Lhs: []ast.Expr{testObj2},
Tok: token.ASSIGN,
Rhs: []ast.Expr{mustCall(getItemByPKCall(testObj))},
},
// if deep.Equal(...) {...}
makeDeepEqual(testObj, testObj2),
)
indexGets, hasIndexedGets := []ast.Stmt{ indexGets, hasIndexedGets := []ast.Stmt{
BlankLine(), BlankLine(),
Comment("Indexed lookups"), Comment("Indexed lookups"),

View File

@@ -76,3 +76,21 @@ func (t Table) HasSoftDelete() bool {
} }
return hasIsDeleted && hasDeletedAt return hasIsDeleted && hasDeletedAt
} }
// GetUpdateableColumns returns the columns that should be written to by an "update" operation.
func (t Table) GetUpdateableColumns() (ret []Column) {
hasCreatedAt, _ := t.HasAutoTimestamps()
for _, col := range t.Columns {
if col.Name == "rowid" || col.IsPrimaryKey {
continue // Rowid / primary key is never updateable
}
if col.Name == "created_at" && hasCreatedAt {
continue // Update shouldn't write `created_at`
}
if t.HasSoftDelete() && (col.Name == "is_deleted" || col.Name == "deleted_at") {
continue // Soft-delete state is managed by SoftDeleteXyz(), not by updates
}
ret = append(ret, col)
}
return
}

34
pkg/schema/table_test.go Normal file
View File

@@ -0,0 +1,34 @@
package schema
import "testing"
func TestTableGetUpdateableColumns(t *testing.T) {
tbl := Table{
Columns: []Column{
{Name: "rowid"},
{Name: "version", Type: "integer", IsPrimaryKey: true},
{Name: "created_at", Type: "integer"},
{Name: "updated_at", Type: "integer"},
{Name: "is_deleted", Type: "integer"},
{Name: "deleted_at", Type: "integer"},
{Name: "owner_id", Type: "integer", IsForeignKey: true},
{Name: "name", Type: "text"},
},
}
gotColumns := tbl.GetUpdateableColumns()
got := make([]string, 0, len(gotColumns))
for _, col := range gotColumns {
got = append(got, col.Name)
}
want := []string{"updated_at", "owner_id", "name"}
if len(got) != len(want) {
t.Fatalf("GetUpdateableColumns() = %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("GetUpdateableColumns() = %v, want %v", got, want)
}
}
}