github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/depends/kit/modelgen/model.go (about) 1 package modelgen 2 3 import ( 4 "bytes" 5 "fmt" 6 "go/types" 7 "path/filepath" 8 "runtime" 9 "sort" 10 "strings" 11 12 g "github.com/machinefi/w3bstream/pkg/depends/gen/codegen" 13 "github.com/machinefi/w3bstream/pkg/depends/kit/sqlx/builder" 14 "github.com/machinefi/w3bstream/pkg/depends/x/mapx" 15 "github.com/machinefi/w3bstream/pkg/depends/x/misc/must" 16 "github.com/machinefi/w3bstream/pkg/depends/x/pkgx" 17 "github.com/machinefi/w3bstream/pkg/depends/x/stringsx" 18 ) 19 20 func NewModel(pkg *pkgx.Pkg, tn *types.TypeName, doc string, cfg *Config) *Model { 21 cfg.SetDefault() 22 m := Model{ 23 TypeName: tn, 24 Config: cfg, 25 Table: builder.T(cfg.TableName), 26 } 27 28 // parse comments for each struct field 29 { 30 31 t := tn.Type().Underlying().Underlying().(*types.Struct) // struct type 32 p := pkg.PkgByPath(tn.Pkg().Path()) // package info 33 // for each struct field var 34 each := func(v *types.Var, name string, tag string) { 35 col := builder.Col(name).Field(v.Name()).Type("", tag) 36 for ident, obj := range p.TypesInfo.Defs { 37 if obj != v { 38 continue 39 } 40 rel, lines := parseColRelFromDoc(pkg.CommentsOf(ident)) 41 if rel != "" { 42 if path := strings.Split(rel, "."); len(path) == 2 { 43 col.Rel = path 44 } else { 45 continue 46 } 47 } 48 if len(lines) > 0 { 49 col.Comment, col.Desc = lines[0], lines 50 } 51 } 52 m.AddColumnAndField(col, v) 53 } 54 forEachStructField(t, each) 55 } 56 57 m.HasDeletedAt = m.Table.ColByFieldName(m.FieldKeyDeletedAt) != nil 58 m.HasCreatedAt = m.Table.ColByFieldName(m.FieldKeyCreatedAt) != nil 59 m.HasUpdatedAt = m.Table.ColByFieldName(m.FieldKeyUpdatedAt) != nil 60 61 keys, lines := parseKeysFromDoc(doc) 62 m.Keys = keys 63 if len(lines) > 0 { 64 m.Desc = lines 65 } 66 67 if m.HasDeletedAt { 68 m.Keys.WithSoftDelete(m.FieldKeyDeletedAt) 69 } 70 m.Keys.Bind(m.Table) 71 72 if col := m.Table.AutoIncrement(); col != nil { 73 m.HasAutoIncrement = true 74 m.FieldKeyAutoIncrement = col.FieldName 75 } 76 77 return &m 78 } 79 80 type Model struct { 81 *types.TypeName 82 *Config 83 *Keys 84 *builder.Table 85 Fields map[string]*types.Var 86 FieldKeyAutoIncrement string 87 HasDeletedAt bool 88 HasCreatedAt bool 89 HasUpdatedAt bool 90 HasAutoIncrement bool 91 } 92 93 func (m *Model) AddColumnAndField(col *builder.Column, tpe *types.Var) { 94 m.Table.Columns.Add(col) 95 if m.Fields == nil { 96 m.Fields = map[string]*types.Var{} 97 } 98 m.Fields[col.FieldName] = tpe 99 } 100 101 func (m *Model) GetComments() map[string]string { 102 comments := map[string]string{} 103 m.Columns.Range(func(c *builder.Column, _ int) { 104 if c.Comment != "" { 105 comments[c.FieldName] = c.Comment 106 } 107 }) 108 return comments 109 } 110 111 func (m *Model) GetColDesc() map[string][]string { 112 desc := map[string][]string{} 113 m.Columns.Range(func(c *builder.Column, _ int) { 114 if len(c.Desc) > 0 { 115 desc[c.FieldName] = c.Desc 116 } 117 }) 118 return desc 119 } 120 121 func (m *Model) GetColRel() map[string][]string { 122 rel := map[string][]string{} 123 m.Columns.Range(func(c *builder.Column, _ int) { 124 if len(c.Rel) == 2 { 125 rel[c.FieldName] = c.Rel 126 } 127 }) 128 return rel 129 } 130 131 func (m *Model) GetIndexFieldNames() []string { 132 names := make([]string, 0) 133 m.Table.Keys.Range(func(k *builder.Key, _ int) { 134 names = append(names, k.Def.FieldNames...) 135 }) 136 names = uniqueStrings(names) 137 names = filterStrings(names, func(s string, i int) bool { 138 return m.HasDeletedAt && s != m.FieldKeyDeletedAt || !m.HasDeletedAt 139 }) 140 sort.Strings(names) 141 return names 142 } 143 144 func (m *Model) Type() g.SnippetType { 145 return g.Type(m.StructName) 146 } 147 148 func (m *Model) IteratorType() g.SnippetType { 149 return g.Type(m.StructName + "Iterator") 150 } 151 152 func (m *Model) IteratorPtrType() g.SnippetType { 153 return g.Star(m.IteratorType()) 154 } 155 156 func (m *Model) PtrType() g.SnippetType { 157 return g.Star(m.Type()) 158 } 159 160 func (m *Model) VarTable() string { 161 return m.StructName + "Table" 162 } 163 164 func (m *Model) FileType(f *g.File, fn string) g.SnippetType { 165 field, ok := m.Fields[fn] 166 if !ok { 167 return nil 168 } 169 typ := field.Type().String() 170 if strings.Contains(typ, ".") { 171 pkg, name := pkgx.ImportPathAndExpose(typ) 172 if pkg != m.TypeName.Pkg().Path() { 173 return g.Type(f.Use(pkg, name)) 174 } 175 return g.Type(name) 176 } 177 return g.BuiltInType(typ) 178 } 179 180 // Snippets: 181 182 // SnippetTableInstanceAndInit generated below 183 // `Model`Table *builder.Table 184 // func init() { `Model`Table = `DB`.Register(&`Model`{}) 185 func (m *Model) SnippetTableInstanceAndInit(f *g.File) []g.Snippet { 186 return []g.Snippet{ 187 g.DeclVar( 188 g.Var( 189 g.Star(g.Type(f.Use(BuilderPkg, "Table"))), 190 m.VarTable(), 191 ), 192 ), 193 194 g.Func().Named("init"). 195 Do( 196 g.Exprer( 197 `?=?.Register(&?{})`, 198 g.Ident(m.VarTable()), 199 g.Ident(m.Database), 200 g.Ident(m.StructName), 201 ), 202 ), 203 } 204 205 } 206 207 // SnippetTableIteratorAndMethods generate below 208 // `Model`Iterator struct{} 209 // func (`Model`Table) New() interface{} { return &`Model`{} } 210 // func (`Model`Table) Resolve(v interface{}) *`Model` { return v.(*`Model`) } 211 func (m *Model) SnippetTableIteratorAndMethods(_ *g.File) []g.Snippet { 212 return []g.Snippet{ 213 g.DeclType(g.Var(g.Struct(), string(m.IteratorType().Bytes()))), 214 215 g.Func(). 216 Named("New"). 217 MethodOf(g.Var(m.IteratorPtrType())). 218 Return(g.Var(g.Interface())). 219 Do(g.Return(g.Exprer("&?{}", m.Type()))), 220 221 g.Func(g.Var(g.Interface(), "v")). 222 Named("Resolve"). 223 MethodOf(g.Var(m.IteratorPtrType())). 224 Return(g.Var(m.PtrType())). 225 Do(g.Return(g.Exprer("v.(?)", m.PtrType()))), 226 } 227 } 228 229 // SnippetTableName generate below 230 // TableName implements builder.Model 231 // func (`Model`) TableName() string 232 func (m *Model) SnippetTableName(f *g.File) g.Snippet { 233 return g.Func().Named("TableName").MethodOf(g.Var(m.PtrType())). 234 Return(g.Var(g.String)). 235 Do(g.Return(f.Value(m.Config.TableName))) 236 } 237 238 // SnippetTableDesc generate below 239 // TableDesc implements builder.WithTableDesc 240 // func (`Model`) TableDesc() []string 241 func (m *Model) SnippetTableDesc(f *g.File) g.Snippet { 242 if len(m.Table.Desc) == 0 { 243 return nil 244 } 245 return g.Func().Named("TableDesc").MethodOf(g.Var(m.PtrType())). 246 Return(g.Var(g.Slice(g.String))). 247 Do(g.Return(f.Value(m.Table.Desc))) 248 } 249 250 // SnippetComments generate below 251 // Comments implements builder.WithComments 252 // func(`Model`) Comments() map[string]string // field_name: comment line 253 func (m *Model) SnippetComments(f *g.File) g.Snippet { 254 if !m.WithComments { 255 return nil 256 } 257 return g.Func().Named("Comments").MethodOf(g.Var(m.PtrType())). 258 Return(g.Var(g.Map(g.String, g.String))). 259 Do(g.Return(f.Value(m.GetComments()))) 260 } 261 262 // SnippetColDesc generate below 263 // ColDesc implements builder.WithColDesc 264 // func (`Model`) ColDesc() map[string][]string 265 func (m *Model) SnippetColDesc(f *g.File) g.Snippet { 266 desc := m.GetColDesc() 267 return g.Func().Named("ColDesc").MethodOf(g.Var(m.PtrType())). 268 Return(g.Var(g.Map(g.String, g.Slice(g.String)))). 269 Do(g.Return(f.Value(desc))) 270 } 271 272 // SnippetColRel generate below 273 // ColRel implements builder.WithColRel 274 // func (`Model`) ColRel() map[string][]string 275 func (m *Model) SnippetColRel(f *g.File) g.Snippet { 276 rel := m.GetColRel() 277 return g.Func().Named("ColRel").MethodOf(g.Var(m.PtrType())). 278 Return(g.Var(g.Map(g.String, g.Slice(g.String)))). 279 Do(g.Return(f.Value(rel))) 280 } 281 282 // SnippetPrimaryKey generate below 283 // PrimaryKey implements builder.WithPrimaryKey 284 // func(`Model`) PrimaryKey() []string 285 func (m *Model) SnippetPrimaryKey(f *g.File) g.Snippet { 286 if len(m.Keys.Primary) == 0 { 287 return nil 288 } 289 return g.Func().Named("PrimaryKey").MethodOf(g.Var(m.PtrType())). 290 Return(g.Var(g.Slice(g.String))). 291 Do(g.Return(f.Value(m.Keys.Primary))) 292 } 293 294 // SnippetIndexes generate below 295 // Indexes implements builder.WithIndexes 296 // func(`Model`) Indexes() builder.Indexes 297 func (m *Model) SnippetIndexes(f *g.File) g.Snippet { 298 if len(m.Keys.Indexes) == 0 { 299 return nil 300 } 301 return g.Func().Named("Indexes").MethodOf(g.Var(m.PtrType())). 302 Return(g.Var(g.Type(f.Use(BuilderPkg, "Indexes")))). 303 Do(g.Return(f.Value(m.Keys.Indexes))) 304 } 305 306 // SnippetIndexFieldNames generate below 307 // func (m *`Model`) IndexFieldNames() []string // index field name list 308 func (m *Model) SnippetIndexFieldNames(f *g.File) g.Snippet { 309 return g.Func().Named("IndexFieldNames").MethodOf(g.Var(m.PtrType(), "m")). 310 Return(g.Var(g.Slice(g.String))). 311 Do(g.Return(f.Value(m.GetIndexFieldNames()))) 312 } 313 314 // SnippetUniqueIndexes generate below 315 // UniqueIndexes() implements `builder.WithUniqueIndexes` 316 // func(`Model`) UniqueIndexes() builder.Indexes [db_index_name->field_names] 317 // func(`Model`) UniqueIndexXXX() string; [for each unique index, XXX is index name] 318 func (m *Model) SnippetUniqueIndexes(f *g.File) []g.Snippet { 319 if len(m.UniqueIndexes) == 0 { 320 return nil 321 } 322 snippets := make([]g.Snippet, 0, len(m.UniqueIndexes)+1) 323 names := make([]string, 0, len(m.UniqueIndexes)) 324 for name := range m.Keys.UniqueIndexes { 325 names = append(names, name) 326 } 327 sort.Strings(names) 328 snippets = append(snippets, 329 g.Func().Named("UniqueIndexes").MethodOf(g.Var(m.PtrType())). 330 Return(g.Var(g.Type(f.Use(BuilderPkg, "Indexes")))). 331 Do(g.Return(f.Value(m.Keys.UniqueIndexes))), 332 ) 333 for _, name := range names { 334 fn := "UniqueIndex" + stringsx.UpperCamelCase(name) 335 snippets = append(snippets, 336 g.Func().Named(fn).MethodOf(g.Var(m.PtrType())). 337 Return(g.Var(g.String)). 338 Do(g.Return(f.Value(name))), 339 ) 340 } 341 return snippets 342 } 343 344 // SnippetFieldMethods generate below 345 // func (m *`Model`) ColXXX() *builder.Column // FOR EACH Field, return field column 346 // func (m `Model`) FieldXXX() string // FOR EACH Field, return field name 347 func (m *Model) SnippetFieldMethods(f *g.File) []g.Snippet { 348 snippets := make([]g.Snippet, 0, 2*m.Columns.Len()) 349 m.Columns.Range(func(c *builder.Column, _ int) { 350 if c.DeprecatedActs != nil { 351 return // TODO generate deprecated actions 352 } 353 fn := "Col" + c.FieldName 354 snippets = append(snippets, 355 g.Func().Named(fn).MethodOf(g.Var(m.PtrType(), "m")). 356 Return(g.Var(g.Star(g.Type(f.Use(BuilderPkg, "Column"))))). 357 Do( 358 g.Return( 359 g.Exprer( 360 "?.ColByFieldName(m.Field"+c.FieldName+"())", 361 g.Ident(m.VarTable()), 362 ), 363 ), 364 ), 365 ) 366 fn = "Field" + c.FieldName 367 snippets = append(snippets, 368 g.Func().Named(fn).MethodOf(g.Var(m.PtrType())). 369 Return(g.Var(g.String)). 370 Do(g.Return(f.Value(c.FieldName))), 371 ) 372 }) 373 return snippets 374 } 375 376 // SnippetCondByValue generate below 377 // func (m *`Model`) CondByValue(DBExecutor) builder.SqlCondition // condition of this 378 func (m *Model) SnippetCondByValue(f *g.File) g.Snippet { 379 if !m.WithMethods { 380 return nil 381 } 382 383 return g.Func(g.Var(g.Type(f.Use(SQLxPkg, `DBExecutor`)), `db`)). 384 Named("CondByValue").MethodOf(g.Var(m.PtrType(), `m`)). 385 Return(g.Var(g.Type(f.Use(BuilderPkg, `SqlCondition`)))). 386 Do( 387 g.DeclVar( 388 g.Assign(g.Var(nil, `tbl`)).By(g.Ref(g.Ident(`db`), g.Call(`T`, g.Ident(`m`)))), 389 g.Assign(g.Var(nil, `fvs`)).By(g.Call(f.Use(BuilderPkg, `FieldValueFromStructByNoneZero`), g.Ident(`m`))), 390 m.DeletedAtCondInitial(f, g.Ident(`tbl`), g.Ident(`cond`)), 391 ), 392 g.Exprer(` 393 for _, fn := range m.IndexFieldNames() { 394 if v, ok := fvs[fn]; ok { 395 cond = append(cond, tbl.ColByFieldName(fn).Eq(v)) 396 delete(fvs, fn) 397 } 398 } 399 if len(cond) == 0 { 400 panic(`+f.Use(`fmt`, "Errorf")+`("no field for indexes has value")) 401 } 402 for fn, v := range fvs { 403 cond = append(cond, tbl.ColByFieldName(fn).Eq(v)) 404 }`, 405 ), 406 g.Return(g.Call(f.Use(BuilderPkg, "And"), g.Exprer(`cond...`))), 407 ) 408 } 409 410 func (m *Model) SetCreatedSnippet(f *g.File) g.Snippet { 411 if !m.HasCreatedAt { 412 return nil 413 } 414 return g.Exprer(` 415 if m.` + m.FieldKeyCreatedAt + `.IsZero() { 416 m.` + m.FieldKeyCreatedAt + `.Set(` + f.Use(`time`, `Now`) + `()) 417 }`) 418 } 419 420 func (m *Model) SetUpdatedSnippet(f *g.File) g.Snippet { 421 if !m.HasUpdatedAt { 422 return nil 423 } 424 return g.Exprer(` 425 if m.` + m.FieldKeyUpdatedAt + `.IsZero() { 426 m.` + m.FieldKeyUpdatedAt + `.Set(` + f.Use(`time`, `Now`) + `()) 427 }`) 428 } 429 430 func (m *Model) SetUpdatedSnippetForFVs(f *g.File, fvs *g.SnippetIdent) g.Snippet { 431 if !m.HasUpdatedAt { 432 return nil 433 } 434 return f.Expr(` 435 if _, ok := ?[?]; !ok { 436 ?[?] = ?{Time: ?()} 437 }`, 438 fvs, g.Valuer(m.FieldKeyUpdatedAt), 439 fvs, g.Valuer(m.FieldKeyUpdatedAt), 440 m.FileType(f, m.FieldKeyUpdatedAt), g.Ident(f.Use(`time`, `Now`)), 441 ) 442 } 443 444 func (m *Model) DeletedAtCondInitial(f *g.File, tbl, cond *g.SnippetIdent) g.SnippetSpec { 445 if !m.HasDeletedAt { 446 return g.Assign(cond). 447 By(g.Call(`make`, g.Slice(g.Type(f.Use(BuilderPkg, `SqlCondition`))), f.Value(0))) 448 } 449 // cond = []builder.SqlCondition{tbl.ColByFieldName("DeletedAt").Eq(0)} 450 return g.Assign(cond). 451 By(g.Exprer( 452 `[]`+f.Use(BuilderPkg, `SqlCondition`)+`{?.ColByFieldName(?).Eq(0)}`, 453 tbl, f.Value(m.FieldKeyDeletedAt), 454 )) 455 } 456 457 func (m *Model) DeletedAtCondAttach(f *g.File, tbl, cond *g.SnippetIdent) g.SnippetSpec { 458 if !m.HasDeletedAt { 459 return nil 460 } 461 return g.Assign(cond).By(g.Call( 462 f.Use(BuilderPkg, `And`), 463 g.Exprer(`?.ColByFieldName(?).Eq(0)`, tbl, m.FieldKeyDeletedAt), 464 cond, 465 )) 466 } 467 468 func (m *Model) SetDeletedSnippetForFVs(f *g.File, fvs *g.SnippetIdent) g.Snippet { 469 if !m.HasUpdatedAt { 470 return nil 471 } 472 return g.Exprer(` 473 if _, ok := ?[?]; !ok { 474 ?[?] = ?{Time: ?()} 475 }`, 476 fvs, g.Valuer(m.FieldKeyDeletedAt), 477 fvs, g.Valuer(m.FieldKeyDeletedAt), 478 m.FileType(f, m.FieldKeyDeletedAt), g.Ident(f.Use(`time`, `Now`)), 479 ) 480 } 481 482 // SnippetCreate generate below 483 // Create to create record by value m 484 // func (m *`Model`) Create(DBExecutor) error // Create by this 485 func (m *Model) SnippetCreate(f *g.File) g.Snippet { 486 if !m.WithMethods { 487 return nil 488 } 489 return g.Func(g.Var(g.Type(f.Use(SQLxPkg, `DBExecutor`)), `db`)).Named(`Create`). 490 MethodOf(g.Var(m.PtrType(), `m`)). 491 Return(g.Var(g.Error)). 492 Do( 493 m.SetCreatedSnippet(f), 494 m.SetUpdatedSnippet(f), 495 g.Exprer(` 496 _, err := db.Exec(?(db, m, nil)) 497 return err`, g.Ident(f.Use(SQLxPkg, `InsertToDB`))), 498 ) 499 } 500 501 // SnippetList generate below 502 // List by condition and additions(offset, size) 503 // func (m *`Model`) List(DBExecutor, SqlCondition, Additions) []`Model` 504 func (m *Model) SnippetList(f *g.File) g.Snippet { 505 if !m.WithMethods { 506 return nil 507 } 508 return g.Func( 509 g.Var(g.Type(f.Use(SQLxPkg, `DBExecutor`)), `db`), 510 g.Var(g.Type(f.Use(BuilderPkg, `SqlCondition`)), `cond`), 511 g.Var(g.Ellipsis(g.Type(f.Use(BuilderPkg, `Addition`))), `adds`), 512 ).Named(`List`).MethodOf(g.Var(m.PtrType(), `m`)). 513 Return( 514 g.Var(g.Slice(g.Type(m.StructName))), 515 g.Var(g.Error), 516 ). 517 Do( 518 g.DeclVar( 519 g.Assign(g.Var(nil, `tbl`)).By(g.Ref(g.Ident(`db`), g.Call(`T`, g.Ident(`m`)))), 520 g.Assign(g.Var(nil, `lst`)).By(g.Call(`make`, g.Slice(g.Type(m.StructName)), g.Valuer(0))), 521 ), 522 m.DeletedAtCondAttach(f, g.Ident(`tbl`), g.Ident(`cond`)), 523 g.Assign(g.Var(nil, `adds`)).By(g.Exprer( 524 `append([]`+f.Use(BuilderPkg, `Addition`)+ 525 `{`+f.Use(BuilderPkg, `Where`)+`(cond), `+ 526 f.Use(BuilderPkg, `Comment`)+`("`+m.StructName+`.List")}, adds...)`, 527 )), 528 g.Exprer(`err := db.QueryAndScan(`+f.Use(BuilderPkg, `Select`)+`(nil).From(tbl, adds...), &lst)`), 529 g.Return(g.Ident(`lst`), g.Ident(`err`)), 530 ) 531 } 532 533 // SnippetCount generate below 534 // Count by condition and addition(offset, size) 535 // func (m *`Model`) Count(DBExecutor, SqlCondition, Additions) (int64, error) 536 func (m *Model) SnippetCount(f *g.File) g.Snippet { 537 if !m.WithMethods { 538 return nil 539 } 540 return g.Func( 541 g.Var(g.Type(f.Use(SQLxPkg, `DBExecutor`)), `db`), 542 g.Var(g.Type(f.Use(BuilderPkg, `SqlCondition`)), `cond`), 543 g.Var(g.Ellipsis(g.Type(f.Use(BuilderPkg, `Addition`))), `adds`), 544 ).Named(`Count`).MethodOf(g.Var(m.PtrType(), `m`)). 545 Return(g.Var(g.Int64, `cnt`), g.Var(g.Error, `err`)). 546 Do( 547 g.Define(g.Var(nil, `tbl`)).By(g.Ref(g.Ident(`db`), g.Call(`T`, g.Ident(`m`)))), 548 m.DeletedAtCondAttach(f, g.Ident(`tbl`), g.Ident(`cond`)), 549 g.Assign(g.Var(nil, `adds`)).By(g.Exprer( 550 `append([]`+f.Use(BuilderPkg, `Addition`)+ 551 `{`+f.Use(BuilderPkg, `Where`)+`(cond), `+ 552 f.Use(BuilderPkg, `Comment`)+`("`+m.StructName+`.List")}, adds...)`, 553 )), 554 g.Exprer(`err = db.QueryAndScan(`+f.Use(BuilderPkg, `Select`)+`(`+f.Use(BuilderPkg, `Count`)+`()).From(tbl, adds...), &cnt)`), 555 g.Return(), 556 ) 557 } 558 559 func IndexCond(f *g.File, fns ...string) string { 560 b := bytes.NewBuffer(nil) 561 b.WriteString(f.Use(BuilderPkg, "And(\n")) 562 for _, fn := range fns { 563 b.WriteString(fmt.Sprintf(`tbl.ColByFieldName("%s").Eq(m.%s),`, fn, fn)) 564 b.WriteRune('\n') 565 } 566 b.WriteString("),") 567 return b.String() 568 } 569 570 // SnippetCRUDByUniqueKeys generate below 571 // UpdateByXXX to update record by value m 572 // XXX is UniqueIndexNames contacted by `And`; function name like `UpdateByNameAndIDAnd...` 573 // func (m *`Model`) UpdateByXXX(DBExecutor, zeros...) 574 // FetchByXXX to select record by some unique index 575 // XXX is UniqueIndexNames contacted by `And`; function name like `FetchByNameAndIDAnd...` 576 // func (m *`Model`) FetchByXXX(DBExecutor) error // XXX is UniqueIndexNames 577 // Delete by value condition 578 // func (m *`Model`) Delete(DBExecutor) error 579 // DeleteByXXX to delete record by some unique index 580 // XXX is UniqueIndexNames contacted by `And`; function name like `DeleteByNameAndIDAnd...` 581 // func (m *`Model`) DeleteByXXX(DBExecutor) error // XXX is UniqueIndexNames 582 // SoftDeleteByXXX to update `DeleteAt` flag as current time 583 // func (m *`Model`) SoftDeleteByXXX(DBExecutor) error // XXX is UniqueIndexNames 584 func (m *Model) SnippetCRUDByUniqueKeys(f *g.File, keys ...string) []g.Snippet { 585 if !m.WithMethods { 586 return nil 587 } 588 fetches := make([]g.Snippet, 0) 589 updates := make([]g.Snippet, 0) 590 deletes := []g.Snippet{ 591 g.Func(g.Var(g.Type(f.Use(SQLxPkg, `DBExecutor`)), `db`)). 592 Named("Delete").MethodOf(g.Var(m.PtrType(), `m`)). 593 Return(g.Var(g.Error)). 594 Do( 595 g.Exprer(`_, err := db.Exec( 596 `+f.Use(BuilderPkg, `Delete`)+`(). 597 From( 598 db.T(m), 599 `+f.Use(BuilderPkg, `Where`)+`(m.CondByValue(db)), 600 `+f.Use(BuilderPkg, `Comment`)+`(?), 601 ), 602 )`, 603 f.Value(m.StructName+".Delete"), 604 ), 605 g.Return(g.Ident(`err`)), 606 ), 607 } 608 609 set := mapx.Set[string]{} 610 if len(keys) > 0 { 611 set, _ = mapx.ToSet(keys, strings.ToLower) 612 } 613 614 names := make([]string, 0, m.Table.Keys.Len()) 615 616 m.Table.Keys.Range(func(k *builder.Key, _ int) { 617 names = append(names, k.Name) 618 }) 619 sort.Strings(names) 620 621 for _, name := range names { 622 k := m.Table.Keys.Key(name) 623 if !k.IsUnique { 624 continue 625 } 626 if len(set) != 0 && !set[strings.ToLower(k.Name)] { 627 continue 628 } 629 fns := k.Def.FieldNames 630 kns := filterStrings(fns, func(s string, _ int) bool { 631 return m.HasDeletedAt && s != m.FieldKeyDeletedAt || !m.HasDeletedAt 632 }) 633 if m.HasDeletedAt && k.IsPrimary() { 634 fns = append(fns, m.FieldKeyDeletedAt) 635 } 636 xxx := strings.Join(kns, "And") 637 mthNameFetchBy := "FetchBy" + xxx 638 639 // FetchByXXX 640 fetches = append(fetches, 641 g.Func(g.Var(g.Type(f.Use(SQLxPkg, `DBExecutor`)), `db`)). 642 Named(mthNameFetchBy).MethodOf(g.Var(m.PtrType(), `m`)). 643 Return(g.Var(g.Error)). 644 Do( 645 g.Exprer(`tbl := db.T(m) 646 err := db.QueryAndScan( 647 `+f.Use(BuilderPkg, `Select`)+`(nil). 648 From( 649 tbl, 650 `+f.Use(BuilderPkg, `Where`)+`( 651 `+IndexCond(f, fns...)+` 652 ), 653 `+f.Use(BuilderPkg, `Comment`)+`(?), 654 ), 655 m, 656 )`, f.Value(m.StructName+"."+mthNameFetchBy)), 657 g.Return(g.Ident(`err`)), 658 ), 659 ) 660 // UpdateByXXXWithFVs 661 mthNameUpdateByWithFVs := "UpdateBy" + xxx + "WithFVs" 662 updates = append(updates, 663 g.Func( 664 g.Var(g.Type(f.Use(SQLxPkg, `DBExecutor`)), `db`), 665 g.Var(g.Type(f.Use(BuilderPkg, `FieldValues`)), `fvs`), 666 ). 667 Named(mthNameUpdateByWithFVs).MethodOf(g.Var(m.PtrType(), `m`)). 668 Return(g.Var(g.Error)). 669 Do( 670 m.SetUpdatedSnippetForFVs(f, g.Ident(`fvs`)), 671 g.Exprer(`tbl := db.T(m) 672 res, err := db.Exec( 673 `+f.Use(BuilderPkg, `Update`)+`(tbl). 674 Where( 675 `+IndexCond(f, fns...)+` 676 `+f.Use(BuilderPkg, `Comment`)+`(?), 677 ). 678 Set(tbl.AssignmentsByFieldValues(fvs)...), 679 ) 680 if err != nil { 681 return err 682 } 683 if affected, _ := res.RowsAffected(); affected == 0 { 684 return m.`+mthNameFetchBy+`(db) 685 } 686 return nil`, 687 f.Value(m.StructName+"."+mthNameUpdateByWithFVs), 688 ), 689 ), 690 ) 691 692 // UpdateByXXX 693 mthNameUpdateBy := "UpdateBy" + xxx 694 updates = append(updates, 695 g.Func( 696 g.Var(g.Type(f.Use(SQLxPkg, `DBExecutor`)), `db`), 697 g.Var(g.Ellipsis(g.String), `zeros`), 698 ). 699 Named(mthNameUpdateBy).MethodOf(g.Var(m.PtrType(), `m`)). 700 Return(g.Var(g.Error)). 701 Do( 702 g.Exprer(`fvs := `+f.Use(BuilderPkg, `FieldValueFromStructByNoneZero`)+`(m, zeros...) 703 return m.`+mthNameUpdateByWithFVs+`(db, fvs)`, 704 ), 705 ), 706 ) 707 708 // DeleteByXXX 709 mthNameDeleteBy := "DeleteBy" + xxx 710 deletes = append(deletes, 711 g.Func(g.Var(g.Type(f.Use(SQLxPkg, `DBExecutor`)), `db`)). 712 Named(mthNameDeleteBy).MethodOf(g.Var(m.PtrType(), `m`)). 713 Return(g.Var(g.Error)). 714 Do( 715 g.Exprer(`tbl := db.T(m) 716 _, err := db.Exec( 717 `+f.Use(BuilderPkg, `Delete`)+`(). 718 From( 719 tbl, 720 `+f.Use(BuilderPkg, `Where`)+`( 721 `+IndexCond(f, fns...)+` 722 ), 723 `+f.Use(BuilderPkg, `Comment`)+`(?), 724 ), 725 )`, 726 f.Value(m.StructName+"."+mthNameDeleteBy), 727 ), 728 g.Return(g.Ident(`err`)), 729 ), 730 ) 731 732 if m.HasDeletedAt { 733 mthNameSoftDeleteBy := "SoftDeleteBy" + xxx 734 deletes = append(deletes, 735 g.Func(g.Var(g.Type(f.Use(SQLxPkg, `DBExecutor`)), `db`)). 736 Named(mthNameSoftDeleteBy).MethodOf(g.Var(m.PtrType(), `m`)). 737 Return(g.Var(g.Error)). 738 Do( 739 g.Exprer(`tbl := db.T(m) 740 fvs := `+f.Use(BuilderPkg, `FieldValues`)+`{}`), 741 m.SetDeletedSnippetForFVs(f, g.Ident(`fvs`)), 742 m.SetUpdatedSnippetForFVs(f, g.Ident(`fvs`)), 743 g.Exprer(`_, err := db.Exec( 744 `+f.Use(BuilderPkg, `Update`)+`(db.T(m)). 745 Where( 746 `+IndexCond(f, fns...)+` 747 `+f.Use(BuilderPkg, `Comment`)+`(?), 748 ). 749 Set(tbl.AssignmentsByFieldValues(fvs)...), 750 ) 751 return err`, 752 f.Value(m.StructName+"."+mthNameSoftDeleteBy)), 753 ), 754 ) 755 } 756 } 757 return append(fetches, append(updates, deletes...)...) 758 } 759 760 func (m *Model) WriteTo(f *g.File) { 761 snippets := make([]g.Snippet, 0) 762 snippets = append(snippets, m.SnippetTableInstanceAndInit(f)...) 763 snippets = append(snippets, m.SnippetTableIteratorAndMethods(f)...) 764 snippets = append(snippets, m.SnippetTableName(f)) 765 snippets = append(snippets, m.SnippetTableDesc(f)) 766 snippets = append(snippets, m.SnippetComments(f)) 767 snippets = append(snippets, m.SnippetColDesc(f)) 768 snippets = append(snippets, m.SnippetColRel(f)) 769 snippets = append(snippets, m.SnippetPrimaryKey(f)) 770 snippets = append(snippets, m.SnippetIndexes(f)) 771 snippets = append(snippets, m.SnippetIndexFieldNames(f)) 772 snippets = append(snippets, m.SnippetUniqueIndexes(f)...) 773 snippets = append(snippets, m.SnippetFieldMethods(f)...) 774 snippets = append(snippets, m.SnippetCondByValue(f)) 775 snippets = append(snippets, m.SnippetCreate(f)) 776 snippets = append(snippets, m.SnippetList(f)) 777 snippets = append(snippets, m.SnippetCount(f)) 778 snippets = append(snippets, m.SnippetCRUDByUniqueKeys(f)...) 779 780 f.WriteSnippet(snippets...) 781 } 782 783 var ( 784 BuilderPkg = "github.com/machinefi/w3bstream/pkg/depends/kit/sqlx/builder" 785 SQLxPkg = "github.com/machinefi/w3bstream/pkg/depends/kit/sqlx" 786 ) 787 788 func init() { 789 _, current, _, _ := runtime.Caller(0) 790 dir := filepath.Join(filepath.Dir(current), "../sqlx") 791 SQLxPkg = must.String(pkgx.PkgIdByPath(dir)) 792 dir = filepath.Join(filepath.Dir(current), "../sqlx/builder") 793 BuilderPkg = must.String(pkgx.PkgIdByPath(dir)) 794 }