codegen: add generator for soft-deletion
This commit is contained in:
@@ -69,6 +69,9 @@ var generate_model = &cobra.Command{
|
|||||||
modelgenerate.GenerateSaveItemFunc(table),
|
modelgenerate.GenerateSaveItemFunc(table),
|
||||||
modelgenerate.GenerateDeleteItemFunc(table),
|
modelgenerate.GenerateDeleteItemFunc(table),
|
||||||
)
|
)
|
||||||
|
if table.HasSoftDelete() {
|
||||||
|
decls = append(decls, modelgenerate.GenerateSoftDeleteItemFunc(table))
|
||||||
|
}
|
||||||
if table.IsWithoutRowid {
|
if table.IsWithoutRowid {
|
||||||
decls = append(decls,
|
decls = append(decls,
|
||||||
modelgenerate.GenerateGetItemBy(table, table.PrimaryKeyColumns()),
|
modelgenerate.GenerateGetItemBy(table, table.PrimaryKeyColumns()),
|
||||||
|
|||||||
@@ -296,6 +296,7 @@ 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 data for building SQL "insert" and "update" strings
|
||||||
for _, col := range tbl.Columns {
|
for _, col := range tbl.Columns {
|
||||||
@@ -312,6 +313,10 @@ func GenerateSaveItemFunc(tbl schema.Table) *ast.FuncDecl {
|
|||||||
if col.Name == "created_at" && hasCreatedAt {
|
if col.Name == "created_at" && hasCreatedAt {
|
||||||
continue
|
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)
|
if !col.IsPrimaryKey { // Don't try to update primary key columns (mainly for w/o rowid tables)
|
||||||
updatePairs = append(updatePairs, col.Name+"="+val)
|
updatePairs = append(updatePairs, col.Name+"="+val)
|
||||||
}
|
}
|
||||||
@@ -828,6 +833,66 @@ func GenerateDeleteItemFunc(tbl schema.Table) *ast.FuncDecl {
|
|||||||
return funcDecl
|
return funcDecl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateSoftDeleteItemFunc produces an AST for the `SoftDeleteXyz()` function, which is only
|
||||||
|
// generated for tables that have both an "is_deleted" and a "deleted_at" column.
|
||||||
|
// E.g., a table with `table.TypeName = "foods"` will produce a "SoftDeleteFood()" function.
|
||||||
|
func GenerateSoftDeleteItemFunc(tbl schema.Table) *ast.FuncDecl {
|
||||||
|
whereClauses := []string{}
|
||||||
|
for _, c := range tbl.PrimaryKeyColumns() {
|
||||||
|
whereClauses = append(whereClauses, fmt.Sprintf("%s = :%s", c.Name, c.Name))
|
||||||
|
}
|
||||||
|
setPairs := []string{"is_deleted = :is_deleted", "deleted_at = :deleted_at"}
|
||||||
|
|
||||||
|
updateStmt := fmt.Sprintf("\n\t update %s\n\t set %s\n\t where %s\n\t",
|
||||||
|
tbl.TableName,
|
||||||
|
strings.Join(setPairs, ",\n\t "),
|
||||||
|
strings.Join(whereClauses, " and "),
|
||||||
|
)
|
||||||
|
|
||||||
|
funcBody := &ast.BlockStmt{
|
||||||
|
List: []ast.Stmt{
|
||||||
|
// item.IsDeleted = true
|
||||||
|
&ast.AssignStmt{
|
||||||
|
Lhs: []ast.Expr{&ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent(tbl.GetColumnByName("is_deleted").GoFieldName())}},
|
||||||
|
Tok: token.ASSIGN,
|
||||||
|
Rhs: []ast.Expr{ast.NewIdent("true")},
|
||||||
|
},
|
||||||
|
// item.DeletedAt = TimestampNow()
|
||||||
|
&ast.AssignStmt{
|
||||||
|
Lhs: []ast.Expr{&ast.SelectorExpr{X: ast.NewIdent(tbl.VarName), Sel: ast.NewIdent(tbl.GetColumnByName("deleted_at").GoFieldName())}},
|
||||||
|
Tok: token.ASSIGN,
|
||||||
|
Rhs: []ast.Expr{&ast.CallExpr{Fun: ast.NewIdent("TimestampNow"), Args: []ast.Expr{}}},
|
||||||
|
},
|
||||||
|
// result := must.Get(db.DB.NamedExec(...))
|
||||||
|
&ast.AssignStmt{
|
||||||
|
Lhs: []ast.Expr{ast.NewIdent("result")},
|
||||||
|
Tok: token.DEFINE,
|
||||||
|
Rhs: []ast.Expr{mustCall(&ast.CallExpr{
|
||||||
|
Fun: &ast.SelectorExpr{X: dbDB, Sel: ast.NewIdent("NamedExec")},
|
||||||
|
Args: []ast.Expr{
|
||||||
|
&ast.BasicLit{Kind: token.STRING, Value: "`" + updateStmt + "`"},
|
||||||
|
ast.NewIdent(tbl.VarName),
|
||||||
|
},
|
||||||
|
})},
|
||||||
|
},
|
||||||
|
MustBeRowsAffected(tbl),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ast.FuncDecl{
|
||||||
|
Doc: &ast.CommentGroup{List: []*ast.Comment{
|
||||||
|
{Text: fmt.Sprintf("// SoftDelete%s sets deleted-at and marks the item as deleted.", tbl.GoTypeName)},
|
||||||
|
}},
|
||||||
|
Recv: dbRecv,
|
||||||
|
Name: ast.NewIdent("SoftDelete" + tbl.GoTypeName),
|
||||||
|
Type: &ast.FuncType{Params: &ast.FieldList{List: []*ast.Field{{
|
||||||
|
Names: []*ast.Ident{ast.NewIdent(tbl.VarName)},
|
||||||
|
Type: &ast.StarExpr{X: ast.NewIdent(tbl.GoTypeName)},
|
||||||
|
}}}, Results: nil},
|
||||||
|
Body: funcBody,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GenerateSQLFieldsConst produces an AST for the `const xyzSQLFields = ...` string.
|
// GenerateSQLFieldsConst produces an AST for the `const xyzSQLFields = ...` string.
|
||||||
func GenerateSQLFieldsConst(tbl schema.Table) *ast.GenDecl {
|
func GenerateSQLFieldsConst(tbl schema.Table) *ast.GenDecl {
|
||||||
columns := make([]string, 0, len(tbl.Columns))
|
columns := make([]string, 0, len(tbl.Columns))
|
||||||
|
|||||||
@@ -69,9 +69,11 @@ 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 the MakeXyz() factory and in the create/update test: the same columns
|
||||||
// that GenerateSaveItemFunc's "update" branch writes to, minus foreign keys (which can't be
|
// that GenerateSaveItemFunc's "update" branch writes to, minus foreign keys (which can't be
|
||||||
// given plausible values here) and the auto-managed "created_at"/"updated_at" columns.
|
// given plausible values here), the auto-managed "created_at"/"updated_at" columns, and the
|
||||||
|
// 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()
|
hasCreatedAt, hasUpdatedAt := tbl.HasAutoTimestamps()
|
||||||
|
hasSoftDelete := tbl.HasSoftDelete()
|
||||||
for _, c := range tbl.Columns {
|
for _, c := range tbl.Columns {
|
||||||
if c.Name == "rowid" || c.IsPrimaryKey || c.IsForeignKey {
|
if c.Name == "rowid" || c.IsPrimaryKey || c.IsForeignKey {
|
||||||
continue
|
continue
|
||||||
@@ -82,6 +84,9 @@ func UpdateTestFields(tbl pkgschema.Table) (ret []pkgschema.Column) {
|
|||||||
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
|
||||||
|
|||||||
@@ -61,3 +61,18 @@ func (t Table) HasAutoTimestamps() (hasCreatedAt bool, hasUpdatedAt bool) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasSoftDelete reports whether this table supports soft-deletion, i.e., whether it has both
|
||||||
|
// an "is_deleted" and a "deleted_at" column.
|
||||||
|
func (t Table) HasSoftDelete() bool {
|
||||||
|
var hasIsDeleted, hasDeletedAt bool
|
||||||
|
for _, c := range t.Columns {
|
||||||
|
if c.Name == "is_deleted" && c.Type == "integer" {
|
||||||
|
hasIsDeleted = true
|
||||||
|
}
|
||||||
|
if c.Name == "deleted_at" && c.Type == "integer" {
|
||||||
|
hasDeletedAt = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasIsDeleted && hasDeletedAt
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user