github.com/octohelm/storage@v0.0.0-20240516030302-1ac2cc1ea347/devpkg/tablegen/gen.go (about)

     1  package Table
     2  
     3  import (
     4  	"context"
     5  	"go/types"
     6  	"reflect"
     7  	"strings"
     8  
     9  	"github.com/octohelm/gengo/pkg/gengo"
    10  	"github.com/octohelm/storage/pkg/sqlbuilder"
    11  	typesx "github.com/octohelm/x/types"
    12  )
    13  
    14  func init() {
    15  	gengo.Register(&tableGen{})
    16  }
    17  
    18  type tableGen struct {
    19  }
    20  
    21  func (*tableGen) Name() string {
    22  	return "table"
    23  }
    24  
    25  func (*tableGen) New(ctx gengo.Context) gengo.Generator {
    26  	return &tableGen{}
    27  }
    28  
    29  func toDefaultTableName(name string, tableGroup string) string {
    30  	if tableGroup != "" && strings.ToLower(tableGroup) != strings.ToLower(name) {
    31  		return gengo.LowerSnakeCase("t_" + tableGroup + "_" + name)
    32  	}
    33  	return gengo.LowerSnakeCase("t_" + name)
    34  }
    35  
    36  func (g *tableGen) GenerateType(c gengo.Context, named *types.Named) error {
    37  	if !named.Obj().Exported() {
    38  		return gengo.ErrSkip
    39  	}
    40  
    41  	if _, ok := named.Underlying().(*types.Struct); ok {
    42  		t, err := g.scanTable(c, named)
    43  		if err != nil {
    44  			return err
    45  		}
    46  
    47  		g.generateIndexInterfaces(c, t, named)
    48  		g.generateTableStatics(c, t, named)
    49  
    50  		return nil
    51  	}
    52  
    53  	return gengo.ErrSkip
    54  }
    55  
    56  func (g *tableGen) generateTableStatics(c gengo.Context, t sqlbuilder.Table, named *types.Named) {
    57  	register := ""
    58  
    59  	tags, _ := c.Doc(named.Obj())
    60  
    61  	if r, ok := tags["gengo:table:register"]; ok {
    62  		if len(r) > 0 {
    63  			register = r[0]
    64  		}
    65  	}
    66  
    67  	if register != "" {
    68  		c.Render(gengo.Snippet{gengo.T: `
    69  func init() {
    70  	@Register.Add(@Type'T)
    71  }
    72  
    73  `,
    74  			"Register": gengo.ID(register),
    75  			"Type":     gengo.ID(named.Obj()),
    76  		})
    77  	}
    78  
    79  	cols := t.Cols()
    80  	keys := t.Keys()
    81  
    82  	c.Render(gengo.Snippet{gengo.T: `
    83  func (table@Type) New() @sqlbuilderModel {
    84  	return &@Type{}
    85  }
    86  
    87  func (t *table@Type) IsNil() bool {
    88  	return t.table.IsNil()
    89  }
    90  
    91  func (t *table@Type) Ex(ctx @contextContext) *@sqlbuilderEx  {
    92  	return t.table.Ex(ctx)
    93  }
    94  
    95  func (t *table@Type) TableName() string {
    96  	return t.table.TableName()
    97  }
    98  
    99  func (t *table@Type) F(name string) @sqlbuilderColumn {
   100  	return t.table.F(name)
   101  }
   102  
   103  func (t *table@Type) K(name string) @sqlbuilderKey {
   104  	return t.table.K(name)
   105  }
   106  
   107  func (t *table@Type) Cols(names ...string) @sqlbuilderColumnCollection {
   108  	return t.table.Cols(names...)
   109  }
   110  
   111  func (t *table@Type) Keys(names ...string) @sqlbuilderKeyCollection {
   112  	return t.table.Keys(names...)
   113  }
   114  
   115  type table@Type struct {
   116  	I indexNameOf@Type
   117  	table @sqlbuilderTable
   118  	@FieldNames
   119  }
   120  
   121  type indexNameOf@Type struct {
   122  	@indexNames
   123  }
   124  
   125  var @Type'T = &table@Type{
   126  	@fieldNameValues
   127  	I: indexNameOf@Type{
   128  		@indexNameValues
   129  	},
   130  	table: @sqlbuilderTableFromModel(&@Type{}),
   131  }
   132  `,
   133  		"Type": gengo.ID(named.Obj()),
   134  
   135  		"contextContext":             gengo.ID("context.Context"),
   136  		"sqlbuilderTableFromModel":   gengo.ID("github.com/octohelm/storage/pkg/sqlbuilder.TableFromModel"),
   137  		"sqlbuilderEx":               gengo.ID("github.com/octohelm/storage/pkg/sqlbuilder.Ex"),
   138  		"sqlbuilderColumn":           gengo.ID("github.com/octohelm/storage/pkg/sqlbuilder.Column"),
   139  		"sqlbuilderColumnCollection": gengo.ID("github.com/octohelm/storage/pkg/sqlbuilder.ColumnCollection"),
   140  		"sqlbuilderKey":              gengo.ID("github.com/octohelm/storage/pkg/sqlbuilder.Key"),
   141  		"sqlbuilderKeyCollection":    gengo.ID("github.com/octohelm/storage/pkg/sqlbuilder.KeyCollection"),
   142  		"sqlbuilderTable":            gengo.ID("github.com/octohelm/storage/pkg/sqlbuilder.Table"),
   143  		"sqlbuilderModel":            gengo.ID("github.com/octohelm/storage/pkg/sqlbuilder.Model"),
   144  
   145  		"FieldNames": func(sw gengo.SnippetWriter) {
   146  			cols.RangeCol(func(col sqlbuilder.Column, idx int) bool {
   147  				if def := col.Def(); def.DeprecatedActions == nil {
   148  					sw.Render(gengo.Snippet{gengo.T: `
   149  @FieldName @sqlbuilderTypedColumn[@FieldType]
   150  `,
   151  						"FieldName":             gengo.ID(col.FieldName()),
   152  						"FieldType":             gengo.ID(def.Type.String()),
   153  						"sqlbuilderTypedColumn": gengo.ID("github.com/octohelm/storage/pkg/sqlbuilder.TypedColumn"),
   154  					})
   155  				}
   156  				return true
   157  			})
   158  		},
   159  
   160  		"fieldNameValues": func(sw gengo.SnippetWriter) {
   161  			cols.RangeCol(func(col sqlbuilder.Column, idx int) bool {
   162  				if def := col.Def(); def.DeprecatedActions == nil {
   163  					sw.Render(gengo.Snippet{gengo.T: `
   164  @FieldName: @sqlbuilderCastCol[@FieldType](@sqlbuilderTableFromModel(&@Type{}).F(@FieldNameValue)),
   165  `,
   166  						"Type":           gengo.ID(named.Obj()),
   167  						"FieldName":      gengo.ID(col.FieldName()),
   168  						"FieldNameValue": col.FieldName(),
   169  						"FieldType":      gengo.ID(def.Type.String()),
   170  
   171  						"sqlbuilderCastCol":        gengo.ID("github.com/octohelm/storage/pkg/sqlbuilder.CastCol"),
   172  						"sqlbuilderTableFromModel": gengo.ID("github.com/octohelm/storage/pkg/sqlbuilder.TableFromModel"),
   173  					})
   174  				}
   175  				return true
   176  			})
   177  		},
   178  
   179  		"indexNames": func(sw gengo.SnippetWriter) {
   180  			keys.RangeKey(func(key sqlbuilder.Key, idx int) bool {
   181  				if key.IsUnique() {
   182  					sw.Render(gengo.Snippet{gengo.T: `
   183  @KeyName @sqlbuilderColumnCollection
   184  `,
   185  						"KeyName":                    gengo.ID(gengo.UpperCamelCase(key.Name())),
   186  						"sqlbuilderColumnCollection": gengo.ID("github.com/octohelm/storage/pkg/sqlbuilder.ColumnCollection"),
   187  					})
   188  				}
   189  				return true
   190  			})
   191  		},
   192  
   193  		"indexNameValues": func(sw gengo.SnippetWriter) {
   194  			keys.RangeKey(func(key sqlbuilder.Key, idx int) bool {
   195  				if key.IsUnique() {
   196  					names := make([]string, 0)
   197  
   198  					key.Columns().RangeCol(func(col sqlbuilder.Column, idx int) bool {
   199  						names = append(names, col.FieldName())
   200  						return true
   201  					})
   202  
   203  					sw.Render(gengo.Snippet{gengo.T: `
   204  @KeyName: @sqlbuilderTableFromModel(&@Type{}).Cols(@keyNames...),
   205  `,
   206  						"KeyName":                  gengo.ID(gengo.UpperCamelCase(key.Name())),
   207  						"Type":                     gengo.ID(named.Obj()),
   208  						"sqlbuilderTableFromModel": gengo.ID("github.com/octohelm/storage/pkg/sqlbuilder.TableFromModel"),
   209  						"keyNames":                 names,
   210  					})
   211  				}
   212  				return true
   213  			})
   214  		},
   215  	})
   216  }
   217  
   218  func (g *tableGen) generateDescriptions(c gengo.Context, t sqlbuilder.Table, named *types.Named) {
   219  	colComments := map[string]string{}
   220  	colDescriptions := map[string][]string{}
   221  	colRelations := map[string][]string{}
   222  
   223  	t.Cols().RangeCol(func(col sqlbuilder.Column, idx int) bool {
   224  		def := col.Def()
   225  
   226  		if def.Comment != "" {
   227  			colComments[col.FieldName()] = def.Comment
   228  		}
   229  		if len(def.Description) > 0 {
   230  			colDescriptions[col.FieldName()] = col.Def().Description
   231  		}
   232  		if len(def.Relation) > 0 {
   233  			colRelations[col.FieldName()] = def.Relation
   234  		}
   235  
   236  		return true
   237  	})
   238  
   239  	if len(colComments) > 0 {
   240  		c.Render(gengo.Snippet{gengo.T: `
   241  func(@Type) Comments() map[string]string {
   242  	return @comments
   243  }`,
   244  			"Type":     gengo.ID(named.Obj()),
   245  			"comments": colComments,
   246  		})
   247  	}
   248  
   249  	if len(colDescriptions) > 0 {
   250  		c.Render(gengo.Snippet{gengo.T: `
   251  func(@Type) ColDescriptions() map[string][]string {
   252  	return @colDescriptions
   253  }`,
   254  			"Type":            gengo.ID(named.Obj()),
   255  			"colDescriptions": colDescriptions,
   256  		})
   257  	}
   258  
   259  	if len(colRelations) > 0 {
   260  		c.Render(gengo.Snippet{gengo.T: `
   261  func(@Type) ColRelations() map[string][]string {
   262  	return @colRelations
   263  }`,
   264  			"Type":         gengo.ID(named.Obj()),
   265  			"colRelations": colRelations,
   266  		})
   267  	}
   268  }
   269  
   270  func (g *tableGen) generateIndexInterfaces(c gengo.Context, t sqlbuilder.Table, named *types.Named) {
   271  	primary := make([]string, 0)
   272  	indexes := sqlbuilder.Indexes{}
   273  	uniqueIndexes := sqlbuilder.Indexes{}
   274  
   275  	t.Keys().RangeKey(func(key sqlbuilder.Key, idx int) bool {
   276  		keyDef := key.(sqlbuilder.KeyDef)
   277  
   278  		if key.IsPrimary() {
   279  			primary = keyDef.ColNameAndOptions()
   280  		} else {
   281  			n := key.Name()
   282  			if method := keyDef.Method(); method != "" {
   283  				n = n + "/" + method
   284  			}
   285  			if key.IsUnique() {
   286  				uniqueIndexes[n] = keyDef.ColNameAndOptions()
   287  			} else {
   288  				indexes[n] = keyDef.ColNameAndOptions()
   289  			}
   290  		}
   291  		return true
   292  	})
   293  
   294  	c.Render(gengo.Snippet{gengo.T: `
   295  func (@Type) TableName() string {
   296  	return @tableName
   297  }
   298  
   299  `,
   300  		"Type":      gengo.ID(named.Obj()),
   301  		"tableName": t.TableName(),
   302  	})
   303  
   304  	if len(primary) > 0 {
   305  		c.Render(gengo.Snippet{gengo.T: `
   306  func (@Type) Primary() []string {
   307  	return @primary
   308  }
   309  
   310  `,
   311  			"Type":    gengo.ID(named.Obj()),
   312  			"primary": primary,
   313  		})
   314  	}
   315  
   316  	if len(uniqueIndexes) > 0 {
   317  		c.Render(gengo.Snippet{gengo.T: `
   318  func (@Type) UniqueIndexes() @sqlbuilderIndexes {
   319  	return @uniqueIndexes
   320  }
   321  
   322  `,
   323  			"Type":              gengo.ID(named.Obj()),
   324  			"sqlbuilderIndexes": gengo.ID(reflect.TypeOf(uniqueIndexes)),
   325  			"uniqueIndexes":     uniqueIndexes,
   326  		})
   327  	}
   328  
   329  	if len(indexes) > 0 {
   330  		c.Render(gengo.Snippet{gengo.T: `
   331  func (@Type) Indexes() @sqlbuilderIndexes {
   332  	return @indexes
   333  }
   334  
   335  `,
   336  			"Type":              gengo.ID(named.Obj()),
   337  			"sqlbuilderIndexes": gengo.ID(reflect.TypeOf(indexes)),
   338  			"indexes":           indexes,
   339  		})
   340  	}
   341  }
   342  
   343  func (g *tableGen) scanTable(c gengo.Context, named *types.Named) (sqlbuilder.Table, error) {
   344  	tags, _ := c.Package(named.Obj().Pkg().Path()).Doc(named.Obj().Pos())
   345  
   346  	tableGroup := ""
   347  
   348  	if r, ok := tags["gengo:table:group"]; ok {
   349  		if len(r) > 0 {
   350  			tableGroup = r[0]
   351  		}
   352  	}
   353  
   354  	tableName := toDefaultTableName(named.Obj().Name(), tableGroup)
   355  	if tn, ok := tags["gengo:table:name"]; ok {
   356  		if n := tn[0]; len(n) > 0 {
   357  			tableName = n
   358  		}
   359  	}
   360  
   361  	t := sqlbuilder.T(tableName)
   362  
   363  	sqlbuilder.EachStructField(context.Background(), typesx.FromTType(named), func(p *sqlbuilder.StructField) bool {
   364  		def := sqlbuilder.ColumnDef{}
   365  
   366  		if tsf, ok := p.Field.(*typesx.TStructField); ok {
   367  			var tags map[string][]string
   368  			var doc []string
   369  
   370  			if pkgPath := p.Field.PkgPath(); pkgPath != "" {
   371  				tags, doc = c.Package(pkgPath).Doc(tsf.Pos())
   372  			} else {
   373  				tags, doc = c.Package(named.Obj().Pkg().Path()).Doc(tsf.Pos())
   374  			}
   375  
   376  			def.Type = p.Type
   377  			def.Comment, def.Description = commentAndDesc(doc)
   378  
   379  			if values, ok := tags["rel"]; ok {
   380  				rel := strings.Split(values[0], ".")
   381  				if len(rel) >= 2 {
   382  					def.Relation = rel
   383  				}
   384  			}
   385  		}
   386  
   387  		col := sqlbuilder.Col(p.Name, sqlbuilder.ColField(p.FieldName), sqlbuilder.ColDef(def))
   388  		t.(sqlbuilder.ColumnCollectionManger).AddCol(col)
   389  		return true
   390  	})
   391  
   392  	if indexes, ok := tags["def"]; ok {
   393  		for i := range indexes {
   394  			def := sqlbuilder.ParseIndexDefine(indexes[i])
   395  			var key sqlbuilder.Key
   396  
   397  			switch def.Kind {
   398  			case "primary":
   399  				key = sqlbuilder.PrimaryKey(nil, sqlbuilder.IndexColNameAndOptions(def.ColNameAndOptions...))
   400  			case "index":
   401  				key = sqlbuilder.Index(def.Name, nil, sqlbuilder.IndexUsing(def.Method), sqlbuilder.IndexColNameAndOptions(def.ColNameAndOptions...))
   402  			case "unique_index":
   403  				key = sqlbuilder.UniqueIndex(def.Name, nil, sqlbuilder.IndexUsing(def.Method), sqlbuilder.IndexColNameAndOptions(def.ColNameAndOptions...))
   404  			}
   405  
   406  			if key != nil {
   407  				t.(sqlbuilder.KeyCollectionManager).AddKey(key)
   408  			}
   409  		}
   410  	}
   411  
   412  	return t, nil
   413  }
   414  
   415  func commentAndDesc(docs []string) (comment string, desc []string) {
   416  	for _, s := range docs {
   417  		if comment == "" && s == "" {
   418  			continue
   419  		}
   420  		if comment == "" {
   421  			comment = s
   422  		} else {
   423  			desc = append(desc, s)
   424  		}
   425  	}
   426  	return
   427  }