Compare commits

..

No commits in common. "04676461ff333c2fce84301b910653dc17b09035" and "5973a2a4b732de414c376b42557650abe9145755" have entirely different histories.

6 changed files with 187 additions and 220 deletions

1
.gitignore vendored
View File

@ -3,4 +3,3 @@ sample_data/data
# Legacy versions # Legacy versions
.cmd-old/ .cmd-old/
pkg/.testapp pkg/.testapp
.claude

View File

@ -26,14 +26,6 @@ TODO: language-server-for-TODO.txt
TODO: auto-migration-checker TODO: auto-migration-checker
- Use `pkg/schema` to test whether a base schema plus a migration equals a new schema - Use `pkg/schema` to test whether a base schema plus a migration equals a new schema
TODO: codegen SaveXyz update path doesn't check foreign keys
- Insert path has FK error handling, but the update path wraps everything in Must
- An update that violates an FK constraint will panic instead of returning an error
TODO: codegen `without rowid` tables properly TODO: codegen `without rowid` tables properly
TODO: generated test file inclues global test DB setup, which is wrong TODO: generated test file inclues global test DB setup, which is wrong
TODO: join-tables
- handle codegen for without rowid tables properly
- add "get all "

View File

@ -7,7 +7,6 @@ sudo docker run --rm -it \
-v "$(go env GOMODCACHE):/gocache-vol/mod-cache" \ -v "$(go env GOMODCACHE):/gocache-vol/mod-cache" \
-e GOMODCACHE=/gocache-vol/mod-cache \ -e GOMODCACHE=/gocache-vol/mod-cache \
-e GOLANGCI_LINT_CACHE=/gocache-vol/lint-cache \ -e GOLANGCI_LINT_CACHE=/gocache-vol/lint-cache \
-v /memory:/memory \
--workdir /code \ --workdir /code \
--net host \ --net host \
gas gas

View File

@ -1,11 +0,0 @@
package modelgenerate
import "go/ast"
// mustCall wraps a call expression in Must(...), producing AST for Must(inner).
func mustCall(inner ast.Expr) *ast.CallExpr {
return &ast.CallExpr{
Fun: ast.NewIdent("Must"),
Args: []ast.Expr{inner},
}
}

View File

@ -13,18 +13,12 @@ import (
"git.offline-twitter.com/offline-labs/gas-stack/pkg/textutils" "git.offline-twitter.com/offline-labs/gas-stack/pkg/textutils"
) )
// --------------- func GenerateIDType(table schema.Table) *ast.GenDecl {
// Helpers // e.g., `type FoodID int`
// --------------- return &ast.GenDecl{
Tok: token.TYPE,
var ( Specs: []ast.Spec{&ast.TypeSpec{Name: ast.NewIdent(table.TypeIDName), Type: ast.NewIdent("int")}},
dbRecv = &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("db")}, Type: ast.NewIdent("DB")}}} }
dbDB = &ast.SelectorExpr{X: ast.NewIdent("db"), Sel: ast.NewIdent("DB")}
fmtErrorf = &ast.SelectorExpr{X: ast.NewIdent("fmt"), Sel: ast.NewIdent("Errorf")}
)
func SQLFieldsConstIdent(tbl schema.Table) *ast.Ident {
return ast.NewIdent(strings.ToLower(tbl.GoTypeName) + "SQLFields")
} }
func fkFieldName(col schema.Column) string { func fkFieldName(col schema.Column) string {
@ -35,19 +29,6 @@ func fkFieldName(col schema.Column) string {
} }
} }
// ---------------
// Generators
// ---------------
// GenerateIDType produces an AST for the model's ID field.
func GenerateIDType(table schema.Table) *ast.GenDecl {
// e.g., `type FoodID int`
return &ast.GenDecl{
Tok: token.TYPE,
Specs: []ast.Spec{&ast.TypeSpec{Name: ast.NewIdent(table.TypeIDName), Type: ast.NewIdent("int")}},
}
}
// GenerateModelAST produces an AST for a struct type corresponding to the model. // GenerateModelAST produces an AST for a struct type corresponding to the model.
// TODO: generate the right field types here based on column types. // TODO: generate the right field types here based on column types.
func GenerateModelAST(table schema.Table) *ast.GenDecl { func GenerateModelAST(table schema.Table) *ast.GenDecl {
@ -222,7 +203,7 @@ func GenerateSaveItemFunc(tbl schema.Table) *ast.FuncDecl {
Tok: token.ASSIGN, Tok: token.ASSIGN,
Rhs: []ast.Expr{ Rhs: []ast.Expr{
&ast.CallExpr{ &ast.CallExpr{
Fun: &ast.SelectorExpr{X: dbDB, Sel: ast.NewIdent("Get")}, Fun: &ast.SelectorExpr{X: &ast.SelectorExpr{X: ast.NewIdent("db"), Sel: ast.NewIdent("DB")}, 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)},
@ -288,17 +269,17 @@ func GenerateSaveItemFunc(tbl schema.Table) *ast.FuncDecl {
// Do create // Do create
List: append( List: append(
func() []ast.Stmt { func() []ast.Stmt {
ret1 := []ast.Stmt{} ret := []ast.Stmt{}
if hasCreatedAt { if hasCreatedAt {
// Auto-timestamps: created_at // Auto-timestamps: created_at
ret1 = append(ret1, &ast.AssignStmt{ ret = append(ret, &ast.AssignStmt{
Lhs: []ast.Expr{&ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent("CreatedAt")}}, Lhs: []ast.Expr{&ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent("CreatedAt")}},
Tok: token.ASSIGN, Tok: token.ASSIGN,
Rhs: []ast.Expr{&ast.CallExpr{Fun: ast.NewIdent("TimestampNow"), Args: []ast.Expr{}}}, Rhs: []ast.Expr{&ast.CallExpr{Fun: ast.NewIdent("TimestampNow"), Args: []ast.Expr{}}},
}) })
} }
namedExecStmt := &ast.CallExpr{ namedExecStmt := &ast.CallExpr{
Fun: &ast.SelectorExpr{X: dbDB, Sel: ast.NewIdent("NamedExec")}, Fun: &ast.SelectorExpr{X: ast.NewIdent("db.DB"), Sel: ast.NewIdent("NamedExec")},
Args: []ast.Expr{ Args: []ast.Expr{
&ast.BasicLit{Kind: token.STRING, Value: "`" + insertStmt + "`"}, &ast.BasicLit{Kind: token.STRING, Value: "`" + insertStmt + "`"},
ast.NewIdent(tbl.VarName), ast.NewIdent(tbl.VarName),
@ -306,14 +287,17 @@ func GenerateSaveItemFunc(tbl schema.Table) *ast.FuncDecl {
} }
if !hasFks { if !hasFks {
// No foreign key checking needed; just use `Must` for brevity // No foreign key checking needed; just use `Must` for brevity
return append(ret1, &ast.AssignStmt{ return append(ret, &ast.AssignStmt{
Lhs: []ast.Expr{ast.NewIdent("result")}, Lhs: []ast.Expr{ast.NewIdent("result")},
Tok: token.DEFINE, Tok: token.DEFINE,
Rhs: []ast.Expr{mustCall(namedExecStmt)}, Rhs: []ast.Expr{&ast.CallExpr{
Fun: ast.NewIdent("Must"),
Args: []ast.Expr{namedExecStmt},
}},
}) })
} }
return append(ret1, return append(ret,
// result, err := db.DB.NamedExec(`...`, u) // result, err := db.DB.NamedExec(`...`, u)
&ast.AssignStmt{ &ast.AssignStmt{
Lhs: []ast.Expr{ Lhs: []ast.Expr{
@ -373,10 +357,13 @@ func GenerateSaveItemFunc(tbl schema.Table) *ast.FuncDecl {
Tok: token.ASSIGN, Tok: token.ASSIGN,
Rhs: []ast.Expr{&ast.CallExpr{ Rhs: []ast.Expr{&ast.CallExpr{
Fun: ast.NewIdent(tbl.TypeIDName), Fun: ast.NewIdent(tbl.TypeIDName),
Args: []ast.Expr{mustCall(&ast.CallExpr{ Args: []ast.Expr{&ast.CallExpr{
Fun: ast.NewIdent("Must"),
Args: []ast.Expr{&ast.CallExpr{
Fun: &ast.SelectorExpr{X: ast.NewIdent("result"), Sel: ast.NewIdent("LastInsertId")}, Fun: &ast.SelectorExpr{X: ast.NewIdent("result"), Sel: ast.NewIdent("LastInsertId")},
Args: []ast.Expr{}, Args: []ast.Expr{},
})}, }},
}},
}}, }},
}, },
), ),
@ -387,22 +374,28 @@ func GenerateSaveItemFunc(tbl schema.Table) *ast.FuncDecl {
&ast.AssignStmt{ &ast.AssignStmt{
Lhs: []ast.Expr{ast.NewIdent("result")}, Lhs: []ast.Expr{ast.NewIdent("result")},
Tok: token.DEFINE, Tok: token.DEFINE,
Rhs: []ast.Expr{mustCall(&ast.CallExpr{ Rhs: []ast.Expr{&ast.CallExpr{
Fun: &ast.SelectorExpr{X: dbDB, Sel: ast.NewIdent("NamedExec")}, Fun: ast.NewIdent("Must"),
Args: []ast.Expr{&ast.CallExpr{
Fun: &ast.SelectorExpr{X: ast.NewIdent("db.DB"), Sel: ast.NewIdent("NamedExec")},
Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: "`" + updateStmt + "`"}, ast.NewIdent(tbl.VarName)}, Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: "`" + updateStmt + "`"}, ast.NewIdent(tbl.VarName)},
})}, }},
}},
}, },
&ast.IfStmt{ &ast.IfStmt{
Cond: &ast.BinaryExpr{ Cond: &ast.BinaryExpr{
X: mustCall(&ast.CallExpr{ X: &ast.CallExpr{
Fun: ast.NewIdent("Must"),
Args: []ast.Expr{&ast.CallExpr{
Fun: &ast.SelectorExpr{X: ast.NewIdent("result"), Sel: ast.NewIdent("RowsAffected")}, Fun: &ast.SelectorExpr{X: ast.NewIdent("result"), Sel: ast.NewIdent("RowsAffected")},
Args: []ast.Expr{}, Args: []ast.Expr{},
}), }},
},
Op: token.NEQ, Op: token.NEQ,
Y: &ast.BasicLit{Kind: token.INT, Value: "1"}, Y: &ast.BasicLit{Kind: token.INT, Value: "1"},
}, },
Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ExprStmt{X: &ast.CallExpr{Fun: ast.NewIdent("panic"), Args: []ast.Expr{&ast.CallExpr{Fun: fmtErrorf, Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"got %s with ID (%%d), so attempted update, but it doesn't exist\"", strings.ToLower(tbl.GoTypeName))}, &ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent("ID")}}}}}}}}, Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ExprStmt{X: &ast.CallExpr{Fun: ast.NewIdent("panic"), Args: []ast.Expr{&ast.CallExpr{Fun: ast.NewIdent("fmt.Errorf"), Args: []ast.Expr{&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"got %s with ID (%%d), so attempted update, but it doesn't exist\"", strings.ToLower(tbl.GoTypeName))}, &ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent("ID")}}}}}}}},
}, },
}, },
}, },
@ -416,7 +409,7 @@ func GenerateSaveItemFunc(tbl schema.Table) *ast.FuncDecl {
} }
funcDecl := &ast.FuncDecl{ funcDecl := &ast.FuncDecl{
Recv: dbRecv, Recv: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("db")}, Type: ast.NewIdent("DB")}}},
Name: ast.NewIdent("Save" + tbl.GoTypeName), Name: ast.NewIdent("Save" + tbl.GoTypeName),
Type: &ast.FuncType{ Type: &ast.FuncType{
Params: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent(tbl.VarName)}, Type: &ast.StarExpr{X: ast.NewIdent(tbl.GoTypeName)}}}}, Params: &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent(tbl.VarName)}, Type: &ast.StarExpr{X: ast.NewIdent(tbl.GoTypeName)}}}},
@ -439,6 +432,7 @@ func getByIDFuncName(tblname string) string {
// GenerateGetItemByIDFunc produces an AST for the `GetXyzByID()` function. // GenerateGetItemByIDFunc produces an AST for the `GetXyzByID()` function.
// E.g., a table with `table.TypeName = "foods"` will produce a "GetFoodByID()" function. // E.g., a table with `table.TypeName = "foods"` will produce a "GetFoodByID()" function.
func GenerateGetItemByIDFunc(tbl schema.Table) *ast.FuncDecl { func GenerateGetItemByIDFunc(tbl schema.Table) *ast.FuncDecl {
recv := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("db")}, Type: ast.NewIdent("DB")}}}
arg := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("id")}, Type: ast.NewIdent(tbl.TypeIDName)}}} arg := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("id")}, Type: ast.NewIdent(tbl.TypeIDName)}}}
result := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("ret")}, Type: ast.NewIdent(tbl.GoTypeName)}, {Names: []*ast.Ident{ast.NewIdent("err")}, Type: ast.NewIdent("error")}}} result := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("ret")}, Type: ast.NewIdent(tbl.GoTypeName)}, {Names: []*ast.Ident{ast.NewIdent("err")}, Type: ast.NewIdent("error")}}}
@ -458,7 +452,7 @@ func GenerateGetItemByIDFunc(tbl schema.Table) *ast.FuncDecl {
&ast.AssignStmt{ &ast.AssignStmt{
Lhs: []ast.Expr{ast.NewIdent("err")}, Lhs: []ast.Expr{ast.NewIdent("err")},
Tok: token.ASSIGN, Tok: token.ASSIGN,
Rhs: []ast.Expr{&ast.CallExpr{Fun: &ast.SelectorExpr{X: dbDB, Sel: ast.NewIdent("Get")}, Args: []ast.Expr{&ast.UnaryExpr{Op: token.AND, X: ast.NewIdent("ret")}, selectExpr, ast.NewIdent("id")}}}, Rhs: []ast.Expr{&ast.CallExpr{Fun: &ast.SelectorExpr{X: ast.NewIdent("db.DB"), Sel: ast.NewIdent("Get")}, Args: []ast.Expr{&ast.UnaryExpr{Op: token.AND, X: ast.NewIdent("ret")}, selectExpr, ast.NewIdent("id")}}},
}, },
&ast.IfStmt{ &ast.IfStmt{
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")}}}, 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")}}},
@ -469,7 +463,7 @@ func GenerateGetItemByIDFunc(tbl schema.Table) *ast.FuncDecl {
} }
funcDecl := &ast.FuncDecl{ funcDecl := &ast.FuncDecl{
Recv: dbRecv, Recv: recv,
Name: ast.NewIdent(getByIDFuncName(tbl.TableName)), Name: ast.NewIdent(getByIDFuncName(tbl.TableName)),
Type: &ast.FuncType{Params: arg, Results: result}, Type: &ast.FuncType{Params: arg, Results: result},
Body: funcBody, Body: funcBody,
@ -481,6 +475,9 @@ func GenerateGetItemByIDFunc(tbl schema.Table) *ast.FuncDecl {
// E.g., a table with `table.TypeName = "foods"` will produce a "GetAllFoods()" function. // E.g., a table with `table.TypeName = "foods"` will produce a "GetAllFoods()" function.
func GenerateGetAllItemsFunc(tbl schema.Table) *ast.FuncDecl { func GenerateGetAllItemsFunc(tbl schema.Table) *ast.FuncDecl {
funcName := "GetAll" + inflection.Plural(tbl.GoTypeName) funcName := "GetAll" + inflection.Plural(tbl.GoTypeName)
recv := &ast.FieldList{List: []*ast.Field{
{Names: []*ast.Ident{ast.NewIdent("db")}, Type: ast.NewIdent("DB")},
}}
result := &ast.FieldList{List: []*ast.Field{ result := &ast.FieldList{List: []*ast.Field{
{Names: []*ast.Ident{ast.NewIdent("ret")}, Type: &ast.ArrayType{Elt: ast.NewIdent(tbl.GoTypeName)}}, {Names: []*ast.Ident{ast.NewIdent("ret")}, Type: &ast.ArrayType{Elt: ast.NewIdent(tbl.GoTypeName)}},
}} }}
@ -490,7 +487,7 @@ func GenerateGetAllItemsFunc(tbl schema.Table) *ast.FuncDecl {
Args: []ast.Expr{ Args: []ast.Expr{
&ast.CallExpr{ &ast.CallExpr{
Fun: &ast.SelectorExpr{ Fun: &ast.SelectorExpr{
X: dbDB, X: ast.NewIdent("db.DB"),
Sel: ast.NewIdent("Select"), Sel: ast.NewIdent("Select"),
}, },
Args: []ast.Expr{ Args: []ast.Expr{
@ -517,7 +514,7 @@ func GenerateGetAllItemsFunc(tbl schema.Table) *ast.FuncDecl {
} }
return &ast.FuncDecl{ return &ast.FuncDecl{
Recv: dbRecv, Recv: recv,
Name: ast.NewIdent(funcName), Name: ast.NewIdent(funcName),
Type: &ast.FuncType{ Type: &ast.FuncType{
Params: &ast.FieldList{}, Params: &ast.FieldList{},
@ -531,6 +528,7 @@ func GenerateGetAllItemsFunc(tbl schema.Table) *ast.FuncDecl {
// E.g., a table with `table.TypeName = "foods"` will produce a "DeleteFood()" function. // E.g., a table with `table.TypeName = "foods"` will produce a "DeleteFood()" function.
func GenerateDeleteItemFunc(tbl schema.Table) *ast.FuncDecl { func GenerateDeleteItemFunc(tbl schema.Table) *ast.FuncDecl {
funcName := "Delete" + tbl.GoTypeName funcName := "Delete" + tbl.GoTypeName
recv := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent("db")}, Type: ast.NewIdent("DB")}}}
arg := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent(tbl.VarName)}, Type: ast.NewIdent(tbl.GoTypeName)}}} arg := &ast.FieldList{List: []*ast.Field{{Names: []*ast.Ident{ast.NewIdent(tbl.VarName)}, Type: ast.NewIdent(tbl.GoTypeName)}}}
funcBody := &ast.BlockStmt{ funcBody := &ast.BlockStmt{
@ -538,19 +536,25 @@ func GenerateDeleteItemFunc(tbl schema.Table) *ast.FuncDecl {
&ast.AssignStmt{ &ast.AssignStmt{
Lhs: []ast.Expr{ast.NewIdent("result")}, Lhs: []ast.Expr{ast.NewIdent("result")},
Tok: token.DEFINE, Tok: token.DEFINE,
Rhs: []ast.Expr{mustCall(&ast.CallExpr{ Rhs: []ast.Expr{&ast.CallExpr{
Fun: &ast.SelectorExpr{X: dbDB, Sel: ast.NewIdent("Exec")}, Fun: ast.NewIdent("Must"),
Args: []ast.Expr{&ast.CallExpr{
Fun: &ast.SelectorExpr{X: ast.NewIdent("db.DB"), Sel: ast.NewIdent("Exec")},
Args: []ast.Expr{ Args: []ast.Expr{
&ast.BasicLit{Kind: token.STRING, Value: "`delete from " + tbl.TableName + " where rowid = ?`"}, &ast.BasicLit{Kind: token.STRING, Value: "`delete from " + tbl.TableName + " where rowid = ?`"},
&ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent("ID")}, &ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent("ID")},
}, },
})}, }},
}},
}, },
&ast.IfStmt{ &ast.IfStmt{
Cond: &ast.BinaryExpr{ Cond: &ast.BinaryExpr{
X: mustCall( X: &ast.CallExpr{
Fun: ast.NewIdent("Must"),
Args: []ast.Expr{
&ast.CallExpr{Fun: &ast.SelectorExpr{X: ast.NewIdent("result"), Sel: ast.NewIdent("RowsAffected")}, Args: []ast.Expr{}}, &ast.CallExpr{Fun: &ast.SelectorExpr{X: ast.NewIdent("result"), Sel: ast.NewIdent("RowsAffected")}, Args: []ast.Expr{}},
), },
},
Op: token.NEQ, Op: token.NEQ,
Y: &ast.BasicLit{Kind: token.INT, Value: "1"}, Y: &ast.BasicLit{Kind: token.INT, Value: "1"},
}, },
@ -558,7 +562,7 @@ func GenerateDeleteItemFunc(tbl schema.Table) *ast.FuncDecl {
&ast.ExprStmt{X: &ast.CallExpr{ &ast.ExprStmt{X: &ast.CallExpr{
Fun: ast.NewIdent("panic"), Fun: ast.NewIdent("panic"),
Args: []ast.Expr{&ast.CallExpr{ Args: []ast.Expr{&ast.CallExpr{
Fun: fmtErrorf, Fun: ast.NewIdent("fmt.Errorf"),
Args: []ast.Expr{ Args: []ast.Expr{
&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"tried to delete %s with ID (%%d) but it doesn't exist\"", strings.ToLower(tbl.GoTypeName))}, &ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf("\"tried to delete %s with ID (%%d) but it doesn't exist\"", strings.ToLower(tbl.GoTypeName))},
&ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent("ID")}, &ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent("ID")},
@ -571,7 +575,7 @@ func GenerateDeleteItemFunc(tbl schema.Table) *ast.FuncDecl {
} }
funcDecl := &ast.FuncDecl{ funcDecl := &ast.FuncDecl{
Recv: dbRecv, Recv: recv,
Name: ast.NewIdent(funcName), Name: ast.NewIdent(funcName),
Type: &ast.FuncType{Params: arg, Results: nil}, Type: &ast.FuncType{Params: arg, Results: nil},
Body: funcBody, Body: funcBody,
@ -602,3 +606,11 @@ func GenerateSQLFieldsConst(tbl schema.Table) *ast.GenDecl {
}, },
} }
} }
// ---------------
// Helpers
// ---------------
func SQLFieldsConstIdent(tbl schema.Table) *ast.Ident {
return ast.NewIdent(strings.ToLower(tbl.GoTypeName) + "SQLFields")
}

View File

@ -61,7 +61,9 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
&ast.AssignStmt{ &ast.AssignStmt{
Lhs: []ast.Expr{ast.NewIdent("db")}, Lhs: []ast.Expr{ast.NewIdent("db")},
Tok: token.DEFINE, Tok: token.DEFINE,
Rhs: []ast.Expr{mustCall(&ast.CallExpr{ Rhs: []ast.Expr{&ast.CallExpr{
Fun: ast.NewIdent("Must"),
Args: []ast.Expr{&ast.CallExpr{
Fun: ast.NewIdent("Create"), Fun: ast.NewIdent("Create"),
Args: []ast.Expr{&ast.CallExpr{ Args: []ast.Expr{&ast.CallExpr{
Fun: ast.NewIdent("fmt.Sprintf"), Fun: ast.NewIdent("fmt.Sprintf"),
@ -70,7 +72,8 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
ast.NewIdent("dbName"), ast.NewIdent("dbName"),
}, },
}}, }},
})}, }},
}},
}, },
// return db // return db
&ast.ReturnStmt{ &ast.ReturnStmt{
@ -110,8 +113,6 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
description1 := `"an item"` description1 := `"an item"`
description2 := `"a big item"` description2 := `"a big item"`
hasCreatedAt, hasUpdatedAt := tbl.HasAutoTimestamps()
testCreateUpdateDelete := &ast.FuncDecl{ testCreateUpdateDelete := &ast.FuncDecl{
Name: ast.NewIdent("TestCreateUpdateDelete" + tbl.GoTypeName), Name: ast.NewIdent("TestCreateUpdateDelete" + tbl.GoTypeName),
Type: &ast.FuncType{ Type: &ast.FuncType{
@ -123,15 +124,7 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
}, },
}, },
Body: &ast.BlockStmt{ Body: &ast.BlockStmt{
List: func() []ast.Stmt { List: []ast.Stmt{
assertNotZero := func(obj *ast.Ident, field string) *ast.ExprStmt {
return &ast.ExprStmt{X: &ast.CallExpr{
Fun: ast.NewIdent("assert.NotZero"),
Args: []ast.Expr{ast.NewIdent("t"), &ast.SelectorExpr{X: obj, Sel: ast.NewIdent(field)}},
}}
}
stmts := []ast.Stmt{
// item := Item{Description: "an item"} // item := Item{Description: "an item"}
&ast.AssignStmt{ &ast.AssignStmt{
Lhs: []ast.Expr{testObj}, Lhs: []ast.Expr{testObj},
@ -158,25 +151,18 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
Fun: ast.NewIdent("require.NotZero"), Fun: ast.NewIdent("require.NotZero"),
Args: []ast.Expr{ast.NewIdent("t"), &ast.SelectorExpr{X: testObj, Sel: ast.NewIdent("ID")}}, Args: []ast.Expr{ast.NewIdent("t"), &ast.SelectorExpr{X: testObj, Sel: ast.NewIdent("ID")}},
}}, }},
}
// After create: assert timestamps are set
if hasCreatedAt {
stmts = append(stmts, assertNotZero(testObj, "CreatedAt"))
}
if hasUpdatedAt {
stmts = append(stmts, assertNotZero(testObj, "UpdatedAt"))
}
stmts = append(stmts,
// item2 := Must(TestDB.GetItemByID(item.ID)) // item2 := Must(TestDB.GetItemByID(item.ID))
&ast.AssignStmt{ &ast.AssignStmt{
Lhs: []ast.Expr{testObj2}, Lhs: []ast.Expr{testObj2},
Tok: token.DEFINE, Tok: token.DEFINE,
Rhs: []ast.Expr{mustCall(&ast.CallExpr{ Rhs: []ast.Expr{&ast.CallExpr{
Fun: ast.NewIdent("Must"),
Args: []ast.Expr{&ast.CallExpr{
Fun: ast.NewIdent("TestDB.Get" + tbl.GoTypeName + "ByID"), Fun: ast.NewIdent("TestDB.Get" + tbl.GoTypeName + "ByID"),
Args: []ast.Expr{&ast.SelectorExpr{X: testObj, Sel: ast.NewIdent("ID")}}, Args: []ast.Expr{&ast.SelectorExpr{X: testObj, Sel: ast.NewIdent("ID")}},
})}, }},
}},
}, },
// assert.Equal(t, "an item", item2.Description) // assert.Equal(t, "an item", item2.Description)
@ -201,25 +187,18 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
Fun: ast.NewIdent("TestDB.Save" + tbl.GoTypeName), Fun: ast.NewIdent("TestDB.Save" + tbl.GoTypeName),
Args: []ast.Expr{&ast.UnaryExpr{Op: token.AND, X: testObj}}, Args: []ast.Expr{&ast.UnaryExpr{Op: token.AND, X: testObj}},
}}, }},
)
// After update: assert timestamps are still set
if hasCreatedAt {
stmts = append(stmts, assertNotZero(testObj, "CreatedAt"))
}
if hasUpdatedAt {
stmts = append(stmts, assertNotZero(testObj, "UpdatedAt"))
}
stmts = append(stmts,
// item2 = Must(TestDB.GetItemByID(item.ID)) // item2 = Must(TestDB.GetItemByID(item.ID))
&ast.AssignStmt{ &ast.AssignStmt{
Lhs: []ast.Expr{testObj2}, Lhs: []ast.Expr{testObj2},
Tok: token.ASSIGN, Tok: token.ASSIGN,
Rhs: []ast.Expr{mustCall(&ast.CallExpr{ Rhs: []ast.Expr{&ast.CallExpr{
Fun: ast.NewIdent("Must"),
Args: []ast.Expr{&ast.CallExpr{
Fun: ast.NewIdent("TestDB.Get" + tbl.GoTypeName + "ByID"), Fun: ast.NewIdent("TestDB.Get" + tbl.GoTypeName + "ByID"),
Args: []ast.Expr{&ast.SelectorExpr{X: testObj, Sel: ast.NewIdent("ID")}}, Args: []ast.Expr{&ast.SelectorExpr{X: testObj, Sel: ast.NewIdent("ID")}},
})}, }},
}},
}, },
// assert.Equal(t, item.Description, item2.Description) // assert.Equal(t, item.Description, item2.Description)
@ -257,10 +236,7 @@ func GenerateModelTestAST(tbl schema.Table, gomodName string) *ast.File {
ast.NewIdent("ErrNotInDB"), ast.NewIdent("ErrNotInDB"),
}, },
}}, }},
) },
return stmts
}(),
}, },
} }