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  }