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 }