From 29787b5521ee24814d6240c475904feb3b6e0942 Mon Sep 17 00:00:00 2001 From: ~wispem-wantex Date: Sat, 14 Feb 2026 18:33:41 -0800 Subject: [PATCH] fix: make codegen not fail for "blob" columns - was previously using "[]byte" as a single `ast.NewIdent`, which made comment+blank-line-insertion reparsing fail --- pkg/codegen/modelgenerate/ast_helpers.go | 37 ++++++++++++--------- pkg/codegen/modelgenerate/generate_model.go | 34 ++++++++++++++++--- pkg/schema/table.go | 23 ------------- 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/pkg/codegen/modelgenerate/ast_helpers.go b/pkg/codegen/modelgenerate/ast_helpers.go index c24d1d8..9390062 100644 --- a/pkg/codegen/modelgenerate/ast_helpers.go +++ b/pkg/codegen/modelgenerate/ast_helpers.go @@ -76,28 +76,35 @@ func FprintWithComments(w io.Writer, file *ast.File) error { return fmt.Errorf("re-parsing pretty-print: %w", err) } - // Convert the tree-of-nodes into a slice-of-nodes - collectNodes := func(node ast.Node) []ast.Node { - var nodes []ast.Node - ast.Inspect(node, func(n ast.Node) bool { - // Filter out comments, as they only appear in the - switch n.(type) { - case *ast.CommentGroup, *ast.Comment: - return false - } - nodes = append(nodes, n) - return true - }) - return nodes - } - // Parallel walk: apply TrailingComments from the side map. // Both trees have identical structure (the reparse is just a positioned copy), // so ast.Inspect visits nodes in the same order. We skip comment nodes to // avoid mismatches from Doc fields. if len(TrailingComments) > 0 { + // Helper: convert the tree-of-nodes into a slice-of-nodes + collectNodes := func(node ast.Node) []ast.Node { + var nodes []ast.Node + ast.Inspect(node, func(n ast.Node) bool { + // Filter out comments, as they only appear in the + switch n.(type) { + case *ast.CommentGroup, *ast.Comment: + return false + } + nodes = append(nodes, n) + return true + }) + return nodes + } + origNodes := collectNodes(file) reparsedNodes := collectNodes(parsed) + if len(origNodes) != len(reparsedNodes) { + panic(fmt.Sprintf( + "origNodes: %d; reparsedNodes: %d. The AST generator is likely generating an invalid AST", + len(origNodes), len(reparsedNodes), + )) + } + for i, orig := range origNodes { text, isOk := TrailingComments[orig] if !isOk { diff --git a/pkg/codegen/modelgenerate/generate_model.go b/pkg/codegen/modelgenerate/generate_model.go index 01a3ae0..8dfec81 100644 --- a/pkg/codegen/modelgenerate/generate_model.go +++ b/pkg/codegen/modelgenerate/generate_model.go @@ -27,6 +27,33 @@ func SQLFieldsConstIdent(tbl schema.Table) *ast.Ident { return ast.NewIdent(strings.ToLower(tbl.GoTypeName) + "SQLFields") } +// GoTypeForColumn returns a type expression for this column. +// +// For most columns this isjust its mapped name as a `ast.NewIdent`, but for "blob" it needs +// a slice expression (`[]byte`). +func GoTypeForColumn(c schema.Column) ast.Expr { + if c.IsNonCodeTableForeignKey() { + return ast.NewIdent(schema.TypenameFromTablename(c.ForeignKeyTargetTable) + "ID") + } + switch c.Type { + case "integer", "int": + if strings.HasPrefix(c.Name, "is_") || strings.HasPrefix(c.Name, "has_") { + return ast.NewIdent("bool") + } else if strings.HasSuffix(c.Name, "_at") { + return ast.NewIdent("Timestamp") + } + return ast.NewIdent("int") + case "text": + return ast.NewIdent("string") + case "real": + return ast.NewIdent("float32") + case "blob": + return &ast.ArrayType{Elt: ast.NewIdent("byte")} + default: + panic("Unrecognized sqlite column type: " + c.Type) + } +} + // --------------- // Generators // --------------- @@ -63,10 +90,9 @@ func GenerateModelAST(table schema.Table) *ast.GenDecl { Tag: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("`db:\"%s\" json:\"%s\"`", col.Name, col.Name)}, }) } else { - typeName := col.GoTypeName() fields = append(fields, &ast.Field{ Names: []*ast.Ident{ast.NewIdent(textutils.SnakeToCamel(col.Name))}, - Type: ast.NewIdent(typeName), + Type: GoTypeForColumn(col), Tag: &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("`db:\"%s\" json:\"%s\"`", col.Name, col.Name)}, }) } @@ -183,7 +209,7 @@ func buildFKCheckLambda(tbl schema.Table) (*ast.AssignStmt, bool) { Fun: ast.NewIdent("NewForeignKeyError"), Args: []ast.Expr{ &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", structFieldName)}, - ast.NewIdent(fmt.Sprintf("%q", col.ForeignKeyTargetTable)), + &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("%q", col.ForeignKeyTargetTable)}, structField, }, }, @@ -478,7 +504,7 @@ func GenerateGetItemByUniqColFunc(tbl schema.Table, col schema.Column) *ast.Func Name: ast.NewIdent("Get" + schema.TypenameFromTablename(tbl.TableName) + "By" + col.GoFieldName()), Type: &ast.FuncType{ Params: &ast.FieldList{List: []*ast.Field{ - {Names: []*ast.Ident{param}, Type: ast.NewIdent(col.GoTypeName())}, + {Names: []*ast.Ident{param}, Type: GoTypeForColumn(col)}, }}, Results: &ast.FieldList{List: []*ast.Field{ {Names: []*ast.Ident{ast.NewIdent("ret")}, Type: ast.NewIdent(tbl.GoTypeName)}, diff --git a/pkg/schema/table.go b/pkg/schema/table.go index 5e3a988..cd98bd4 100644 --- a/pkg/schema/table.go +++ b/pkg/schema/table.go @@ -57,29 +57,6 @@ func (c Column) GoVarName() string { return strings.ToLower(fieldname)[0:1] + fieldname[1:] } -func (c Column) GoTypeName() string { - if c.IsNonCodeTableForeignKey() { - return TypenameFromTablename(c.ForeignKeyTargetTable) + "ID" - } - switch c.Type { - case "integer", "int": - if strings.HasPrefix(c.Name, "is_") || strings.HasPrefix(c.Name, "has_") { - return "bool" - } else if strings.HasSuffix(c.Name, "_at") { - return "Timestamp" - } - return "int" - case "text": - return "string" - case "real": - return "float32" - case "blob": - return "[]byte" - default: - panic("Unrecognized sqlite column type: " + c.Type) - } -} - // Table is a single SQLite table. type Table struct { TableName string `db:"name"`