diff --git a/auto_registry_test.go b/auto_registry_test.go new file mode 100644 index 00000000..a7601998 --- /dev/null +++ b/auto_registry_test.go @@ -0,0 +1,287 @@ +package gen + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "gorm.io/driver/sqlite" + "gorm.io/gen/internal/model" + "gorm.io/gorm" +) + +func TestAutoRegistryInitGeneration(t *testing.T) { + // 创建临时目录 + tempDir := t.TempDir() + + // 创建测试数据库 + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + if err != nil { + t.Fatalf("failed to create test database: %v", err) + } + + // 创建简单的测试表 + err = db.Exec(`CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)`).Error + if err != nil { + t.Fatalf("failed to create users table: %v", err) + } + + err = db.Exec(`CREATE TABLE orders (id INTEGER PRIMARY KEY, amount DECIMAL)`).Error + if err != nil { + t.Fatalf("failed to create orders table: %v", err) + } + + tests := []struct { + name string + configTables []string + expectInitFunc map[string]bool // table -> shouldHaveInit + }{ + { + name: "AllTables", + configTables: []string{}, // 空数组表示所有表 + expectInitFunc: map[string]bool{ + "users": true, + "orders": true, + }, + }, + { + name: "OnlyUsersTable", + configTables: []string{"users"}, + expectInitFunc: map[string]bool{ + "users": true, + "orders": false, + }, + }, + { + name: "BothTables", + configTables: []string{"users", "orders"}, + expectInitFunc: map[string]bool{ + "users": true, + "orders": true, + }, + }, + { + name: "NoTables", + configTables: []string{"nonexistent"}, + expectInitFunc: map[string]bool{ + "users": false, + "orders": false, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + modelDir := filepath.Join(tempDir, tt.name, "model") + + // 配置生成器 + g := NewGenerator(Config{ + OutPath: filepath.Join(tempDir, tt.name, "query"), + ModelPkgPath: modelDir, + Mode: WithDefaultQuery | WithAutoRegistry, + }) + + // 根据测试配置启用自动注册 + if len(tt.configTables) == 0 { + g.WithAutoRegistry() // 不传参数,所有表 + } else { + g.WithAutoRegistry(tt.configTables...) // 传入指定表名 + } + + g.UseDB(db) + g.GenerateAllTable() + g.Execute() + + // 验证每个表的 init 函数生成情况 + for tableName, shouldHaveInit := range tt.expectInitFunc { + t.Run(tableName, func(t *testing.T) { + checkInitFunction(t, modelDir, tableName, shouldHaveInit) + }) + } + + // 验证注册表文件是否生成 + registryFile := filepath.Join(modelDir, "gen.go") + if _, err := os.Stat(registryFile); os.IsNotExist(err) { + t.Errorf("registry file %s should exist", registryFile) + } + }) + } +} + +// checkInitFunction 检查指定表的模型文件是否包含正确的 init 函数 +func checkInitFunction(t *testing.T, modelDir, tableName string, shouldHaveInit bool) { + fileName := filepath.Join(modelDir, tableName+".gen.go") + + // 检查文件是否存在 + if _, err := os.Stat(fileName); os.IsNotExist(err) { + t.Errorf("model file %s does not exist", fileName) + return + } + + // 读取文件内容 + content, err := os.ReadFile(fileName) + if err != nil { + t.Errorf("failed to read file %s: %v", fileName, err) + return + } + + fileContent := string(content) + + // 检查是否包含 init 函数 + hasInitFunc := strings.Contains(fileContent, "func init() {") + hasRegisterCall := strings.Contains(fileContent, "RegisterModel(") + + if shouldHaveInit { + if !hasInitFunc { + t.Errorf("file %s should contain 'func init() {' but doesn't", fileName) + } + if !hasRegisterCall { + t.Errorf("file %s should contain 'RegisterModel(' call but doesn't", fileName) + } + + // 验证 RegisterModel 调用格式 + if hasInitFunc && hasRegisterCall { + expectedModelName := getExpectedModelName(tableName) + expectedCall := "RegisterModel(&" + expectedModelName + "{}, TableName" + expectedModelName + ")" + + if !strings.Contains(fileContent, expectedCall) { + t.Errorf("file %s should contain %s", fileName, expectedCall) + t.Logf("Actual file content:\n%s", fileContent) + } + } + } else { + if hasInitFunc && hasRegisterCall { + t.Errorf("file %s should not contain init function with RegisterModel call", fileName) + } + } +} + +// getExpectedModelName 根据表名获取期望的模型名 +func getExpectedModelName(tableName string) string { + switch tableName { + case "users": + return "User" + case "orders": + return "Order" + default: + // 简单的首字母大写 + return model.TitleCase(tableName) + } +} + +// TestShouldEnableAutoRegistry 测试表过滤逻辑 +func TestShouldEnableAutoRegistry(t *testing.T) { + tests := []struct { + name string + configuredList []string + tableName string + expected bool + }{ + { + name: "EmptyList_AllTablesEnabled", + configuredList: []string{}, + tableName: "users", + expected: true, + }, + { + name: "TableInList_ShouldEnable", + configuredList: []string{"users", "orders"}, + tableName: "users", + expected: true, + }, + { + name: "TableNotInList_ShouldDisable", + configuredList: []string{"users", "orders"}, + tableName: "products", + expected: false, + }, + { + name: "SingleTable_Match", + configuredList: []string{"users"}, + tableName: "users", + expected: true, + }, + { + name: "SingleTable_NoMatch", + configuredList: []string{"users"}, + tableName: "orders", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &Generator{ + Config: Config{ + RegistryTableList: tt.configuredList, + }, + } + + result := g.shouldEnableAutoRegistry(tt.tableName) + if result != tt.expected { + t.Errorf("shouldEnableAutoRegistry(%s) = %v, want %v", + tt.tableName, result, tt.expected) + } + }) + } +} + +// TestWithAutoRegistryConfig 测试配置方法 +func TestWithAutoRegistryConfig(t *testing.T) { + tests := []struct { + name string + tableNames []string + expectedList []string + expectedMode GenerateMode + }{ + { + name: "NoTables", + tableNames: []string{}, + expectedList: []string{}, + expectedMode: WithAutoRegistry, + }, + { + name: "SingleTable", + tableNames: []string{"users"}, + expectedList: []string{"users"}, + expectedMode: WithAutoRegistry, + }, + { + name: "MultipleTables", + tableNames: []string{"users", "orders", "products"}, + expectedList: []string{"users", "orders", "products"}, + expectedMode: WithAutoRegistry, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := Config{} + + // 调用 WithAutoRegistry 方法 + cfg.WithAutoRegistry(tt.tableNames...) + + // 验证配置结果 + if cfg.Mode&WithAutoRegistry == 0 { + t.Error("WithAutoRegistry mode should be enabled") + } + + if len(cfg.RegistryTableList) != len(tt.expectedList) { + t.Errorf("RegistryTableList length = %d, want %d", + len(cfg.RegistryTableList), len(tt.expectedList)) + } + + for i, expected := range tt.expectedList { + if i >= len(cfg.RegistryTableList) || cfg.RegistryTableList[i] != expected { + actual := "" + if i < len(cfg.RegistryTableList) { + actual = cfg.RegistryTableList[i] + } + t.Errorf("RegistryTableList[%d] = %s, want %s", + i, actual, expected) + } + } + }) + } +} diff --git a/config.go b/config.go index eca1fa9f..953587ef 100644 --- a/config.go +++ b/config.go @@ -27,6 +27,9 @@ const ( // WithGeneric generate code with generic WithGeneric + + // WithAutoRegistry generate init functions to auto-register models + WithAutoRegistry ) // Config generator's basic configuration @@ -38,6 +41,9 @@ type Config struct { ModelPkgPath string // generated model code's package name WithUnitTest bool // generate unit test for query code + // auto registry configuration + RegistryTableList []string // specific table names to enable auto registry, empty means all tables + // generate model global configuration FieldNullable bool // generate pointer when field is nullable FieldCoverable bool // generate pointer when field has default value, to fix problem zero value cannot be assign: https://gorm.io/docs/create.html#Default-Values @@ -106,6 +112,13 @@ func (cfg *Config) WithJSONTagNameStrategy(ns func(columnName string) (tagConten cfg.fieldJSONTagNS = ns } +// WithAutoRegistry enable auto registry feature for generated models +// tableNames: optional table names to enable auto registry, if empty, all tables will be enabled +func (cfg *Config) WithAutoRegistry(tableNames ...string) { + cfg.Mode |= WithAutoRegistry + cfg.RegistryTableList = tableNames +} + // WithImportPkgPath specify import package path func (cfg *Config) WithImportPkgPath(paths ...string) { for i, path := range paths { diff --git a/examples/biz/query.go b/examples/biz/query.go index 01042bae..f5284495 100644 --- a/examples/biz/query.go +++ b/examples/biz/query.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "gorm.io/gen/examples/dal/query" + "examples/dal/query" ) var q = query.Q diff --git a/examples/cmd/gen/generate.go b/examples/cmd/gen/generate.go index 0630e7c0..9c806b31 100644 --- a/examples/cmd/gen/generate.go +++ b/examples/cmd/gen/generate.go @@ -1,24 +1,29 @@ package main import ( + "examples/conf" + "examples/dal" + "gorm.io/gen" - "gorm.io/gen/examples/conf" - "gorm.io/gen/examples/dal" ) func init() { - dal.DB = dal.ConnectDB(conf.MySQLDSN).Debug() + dal.DB = dal.ConnectDB(conf.SQLiteDBName).Debug() prepare(dal.DB) // prepare table for generate } func main() { g := gen.NewGenerator(gen.Config{ - OutPath: "../../dal/query", + OutPath: "../../dal/query", + ModelPkgPath: "../../dal/model", }) g.UseDB(dal.DB) + // auto registry to models + g.WithAutoRegistry() + // generate all table from database g.ApplyBasic(g.GenerateAllTable()...) diff --git a/examples/cmd/gen/prepare.go b/examples/cmd/gen/prepare.go index e0263dc9..bf6d65b0 100644 --- a/examples/cmd/gen/prepare.go +++ b/examples/cmd/gen/prepare.go @@ -7,13 +7,15 @@ import ( // prepare table for test const mytableSQL = "CREATE TABLE IF NOT EXISTS `mytables` (" + - " `ID` int(11) NOT NULL," + - " `username` varchar(16) DEFAULT NULL," + - " `age` int(8) NOT NULL," + - " `phone` varchar(11) NOT NULL," + - " INDEX `idx_username` (`username`)" + - ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;" + " `ID` INTEGER NOT NULL PRIMARY KEY," + + " `username` TEXT," + + " `age` INTEGER NOT NULL," + + " `phone` TEXT NOT NULL" + + ");" + +const indexSQL = "CREATE INDEX IF NOT EXISTS `idx_username` ON `mytables` (`username`);" func prepare(db *gorm.DB) { db.Exec(mytableSQL) + db.Exec(indexSQL) } diff --git a/examples/cmd/only_model/generate.go b/examples/cmd/only_model/generate.go index a5607459..22a6863b 100644 --- a/examples/cmd/only_model/generate.go +++ b/examples/cmd/only_model/generate.go @@ -1,9 +1,10 @@ package main import ( + "examples/conf" + "examples/dal" + "gorm.io/gen" - "gorm.io/gen/examples/conf" - "gorm.io/gen/examples/dal" ) func init() { diff --git a/examples/cmd/sync_table/generate.go b/examples/cmd/sync_table/generate.go index d2142470..79c70105 100644 --- a/examples/cmd/sync_table/generate.go +++ b/examples/cmd/sync_table/generate.go @@ -3,9 +3,9 @@ package main import ( "strings" + "examples/conf" + "examples/dal" "gorm.io/gen" - "gorm.io/gen/examples/conf" - "gorm.io/gen/examples/dal" "gorm.io/gorm" ) diff --git a/examples/cmd/ultimate/generate.go b/examples/cmd/ultimate/generate.go index ff306104..81d6eb49 100644 --- a/examples/cmd/ultimate/generate.go +++ b/examples/cmd/ultimate/generate.go @@ -1,10 +1,10 @@ package main import ( + "examples/conf" + "examples/dal" + "examples/dal/model" "gorm.io/gen" - "gorm.io/gen/examples/conf" - "gorm.io/gen/examples/dal" - "gorm.io/gen/examples/dal/model" "gorm.io/gorm" ) diff --git a/examples/cmd/without_db/generate.go b/examples/cmd/without_db/generate.go index dfe7bdca..f2aad0ed 100644 --- a/examples/cmd/without_db/generate.go +++ b/examples/cmd/without_db/generate.go @@ -1,8 +1,8 @@ package main import ( + "examples/dal/model" "gorm.io/gen" - "gorm.io/gen/examples/dal/model" ) func main() { diff --git a/examples/dal/model/gen.go b/examples/dal/model/gen.go new file mode 100644 index 00000000..26798dbd --- /dev/null +++ b/examples/dal/model/gen.go @@ -0,0 +1,75 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package model + +import ( + "reflect" + "sync" +) + +// ModelRegistry holds information about registered models in this package +type ModelRegistry struct { + mu sync.RWMutex + models []interface{} + names []string +} + +// registry is the package-level model registry +var registry = &ModelRegistry{ + models: make([]interface{}, 0), + names: make([]string, 0), +} + +// RegisterModel registers a model to the package registry +func RegisterModel(model interface{}, name string) { + registry.RegisterModel(model, name) +} + +// RegisterModel registers a model to this registry +func (r *ModelRegistry) RegisterModel(model interface{}, name string) { + r.mu.Lock() + defer r.mu.Unlock() + + // Check if already registered + for i, existing := range r.models { + if reflect.TypeOf(existing) == reflect.TypeOf(model) { + r.names[i] = name // Update name if type already exists + return + } + } + + r.models = append(r.models, model) + r.names = append(r.names, name) +} + +// GetAllModels returns all registered models in this package +func GetAllModels() []interface{} { + return registry.GetAllModels() +} + +// GetAllModels returns all registered models +func (r *ModelRegistry) GetAllModels() []interface{} { + r.mu.RLock() + defer r.mu.RUnlock() + + result := make([]interface{}, len(r.models)) + copy(result, r.models) + return result +} + +// GetAllModelNames returns all registered model names in this package +func GetAllModelNames() []string { + return registry.GetAllModelNames() +} + +// GetAllModelNames returns all registered model names +func (r *ModelRegistry) GetAllModelNames() []string { + r.mu.RLock() + defer r.mu.RUnlock() + + result := make([]string, len(r.names)) + copy(result, r.names) + return result +} diff --git a/examples/dal/model/mytables.gen.go b/examples/dal/model/mytables.gen.go index 2fbba28c..289bbdd8 100644 --- a/examples/dal/model/mytables.gen.go +++ b/examples/dal/model/mytables.gen.go @@ -8,10 +8,14 @@ const TableNameMytable = "mytables" // Mytable mapped from table type Mytable struct { - ID int32 `gorm:"column:ID;type:int(11);not null" json:"ID_example"` - Username *string `gorm:"column:username;type:varchar(16);index:idx_username,priority:1;default:NULL" json:"username_example"` - Age int32 `gorm:"column:age;type:int(8);not null" json:"age_example"` - Phone string `gorm:"column:phone;type:varchar(11);not null" json:"phone_example"` + ID int32 `gorm:"column:ID;primaryKey" json:"ID"` + Username string `gorm:"column:username" json:"username"` + Age int32 `gorm:"column:age;not null" json:"age"` + Phone string `gorm:"column:phone;not null" json:"phone"` +} + +func init() { + RegisterModel(&Mytable{}, TableNameMytable) } // TableName Mytable's table name diff --git a/examples/generate.sh b/examples/generate.sh index ccf8b226..449b108c 100755 --- a/examples/generate.sh +++ b/examples/generate.sh @@ -1,9 +1,9 @@ #!/bin/bash -# TARGET_DIR="gen" +TARGET_DIR="gen" # TARGET_DIR="ultimate" # TARGET_DIR="sync_table" -TARGET_DIR="without_db" +# TARGET_DIR="without_db" PROJECT_DIR=$(dirname "$0") GENERATE_DIR="$PROJECT_DIR/cmd/$TARGET_DIR" diff --git a/examples/go.mod b/examples/go.mod index cc832c8b..e870d844 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -3,23 +3,26 @@ module examples go 1.19 require ( - gorm.io/driver/mysql v1.5.6 + gorm.io/driver/mysql v1.5.7 gorm.io/driver/sqlite v1.5.5 gorm.io/gen v0.3.25 - gorm.io/gorm v1.25.9 + gorm.io/gorm v1.25.12 ) require ( - github.com/go-sql-driver/mysql v1.7.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/sys v0.14.0 // indirect - golang.org/x/tools v0.15.0 // indirect - gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c // indirect + golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.17.0 // indirect + gorm.io/datatypes v1.2.4 // indirect gorm.io/hints v1.1.0 // indirect - gorm.io/plugin/dbresolver v1.5.0 // indirect + gorm.io/plugin/dbresolver v1.5.3 // indirect ) replace gorm.io/gen => ../ diff --git a/examples/go.sum b/examples/go.sum index 76607200..ba4f46d5 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -1,22 +1,21 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= -github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= -github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= @@ -26,37 +25,63 @@ github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLg github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= -golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c h1:jWdr7cHgl8c/ua5vYbR2WhSp+NQmzhsj0xoY3foTzW8= -gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c/go.mod h1:SH2K9R+2RMjuX1CkCONrPwoe9JzVv2hkQvEu4bXGojE= -gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= -gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= -gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= -gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc= +gorm.io/datatypes v1.2.4 h1:uZmGAcK/QZ0uyfCuVg0VQY1ZmV9h1fuG0tMwKByO1z4= +gorm.io/datatypes v1.2.4/go.mod h1:f4BsLcFAX67szSv8svwLRjklArSHAvHLeE3pXAS5DZI= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= gorm.io/driver/sqlite v1.1.6/go.mod h1:W8LmC/6UvVbHKah0+QOC7Ja66EaZXHwUTjgXY8YNWX8= gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= -gorm.io/gen v0.3.25 h1:uT/1YfvcnYUdike4XPYyi89FEnVHZF115GUXQm2Sfug= -gorm.io/gen v0.3.25/go.mod h1:p+t0iCKjaPz+pKRxcx63nXdRgnrah/QD2l92747ihyA= gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= gorm.io/gorm v1.22.2/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= -gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= -gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= -gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8= -gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= gorm.io/hints v1.1.0 h1:Lp4z3rxREufSdxn4qmkK3TLDltrM10FLTHiuqwDPvXw= gorm.io/hints v1.1.0/go.mod h1:lKQ0JjySsPBj3uslFzY3JhYDtqEwzm+G1hv8rWujB6Y= -gorm.io/plugin/dbresolver v1.5.0 h1:XVHLxh775eP0CqVh3vcfJtYqja3uFl5Wr3cKlY8jgDY= -gorm.io/plugin/dbresolver v1.5.0/go.mod h1:l4Cn87EHLEYuqUncpEeTC2tTJQkjngPSD+lo8hIvcT0= +gorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU= +gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE= diff --git a/generator.go b/generator.go index 2ef41c94..d985c4af 100644 --- a/generator.go +++ b/generator.go @@ -515,6 +515,14 @@ func (g *Generator) generateModelFile() error { go func(data *generate.QueryStructMeta) { defer pool.Done() + // Set auto registry fields if enabled + if g.judgeMode(WithAutoRegistry) { + // Check if this table should have auto registry + if g.shouldEnableAutoRegistry(data.TableName) { + data.WithAutoRegistry = true + } + } + var buf bytes.Buffer err := render(tmpl.Model, &buf, data) if err != nil { @@ -546,9 +554,59 @@ func (g *Generator) generateModelFile() error { case <-pool.AsyncWaitAll(): g.fillModelPkgPath(modelOutPath) } + + // Generate registry file if auto registry is enabled + if g.judgeMode(WithAutoRegistry) { + err = g.generateModelRegistryFile(modelOutPath) + if err != nil { + return fmt.Errorf("generate model registry file fail: %w", err) + } + } + + return nil +} + +// generateModelRegistryFile generate model registry file +func (g *Generator) generateModelRegistryFile(modelOutPath string) error { + var buf bytes.Buffer + + // Get model package name from path + modelPkgName := filepath.Base(strings.TrimSuffix(modelOutPath, string(os.PathSeparator))) + + err := render(tmpl.ModelRegistry, &buf, map[string]interface{}{ + "Package": modelPkgName, + }) + if err != nil { + return err + } + + registryFile := filepath.Join(modelOutPath, "gen.go") + err = g.output(registryFile, buf.Bytes()) + if err != nil { + return err + } + + g.info(fmt.Sprintf("generate model registry file: %s", registryFile)) return nil } +// shouldEnableAutoRegistry check if auto registry should be enabled for the given table +func (g *Generator) shouldEnableAutoRegistry(tableName string) bool { + // If no specific tables configured, enable for all tables + if len(g.RegistryTableList) == 0 { + return true + } + + // Check if table name is in the configured list + for _, configuredTable := range g.RegistryTableList { + if configuredTable == tableName { + return true + } + } + + return false +} + func (g *Generator) getModelOutputPath() (outPath string, err error) { if strings.Contains(g.ModelPkgPath, string(os.PathSeparator)) { outPath, err = filepath.Abs(g.ModelPkgPath) diff --git a/go.mod b/go.mod index 8e561093..03c7a51f 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,10 @@ go 1.18 require ( golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 + golang.org/x/text v0.14.0 golang.org/x/tools v0.17.0 gorm.io/datatypes v1.2.4 + gorm.io/driver/sqlite v1.4.3 gorm.io/gorm v1.25.12 gorm.io/hints v1.1.0 gorm.io/plugin/dbresolver v1.5.3 @@ -17,8 +19,8 @@ require ( github.com/google/uuid v1.3.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-sqlite3 v1.14.15 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/text v0.14.0 // indirect gorm.io/driver/mysql v1.5.7 // indirect ) diff --git a/go.sum b/go.sum index 331612ad..ba2f441f 100644 --- a/go.sum +++ b/go.sum @@ -16,10 +16,12 @@ github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -74,9 +76,11 @@ gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkD gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= gorm.io/driver/sqlite v1.1.6/go.mod h1:W8LmC/6UvVbHKah0+QOC7Ja66EaZXHwUTjgXY8YNWX8= gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= +gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= gorm.io/gorm v1.22.2/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/internal/generate/query.go b/internal/generate/query.go index 5c172dfd..b9941a73 100644 --- a/internal/generate/query.go +++ b/internal/generate/query.go @@ -42,6 +42,9 @@ type QueryStructMeta struct { interfaceMode bool UseGenericMode bool // use generic mode + + // auto registry fields + WithAutoRegistry bool // whether to generate auto registry init function } // parseStruct get all elements of struct with gorm's Parse, ignore unexported elements diff --git a/internal/model/base.go b/internal/model/base.go index 010c641e..5cbac123 100644 --- a/internal/model/base.go +++ b/internal/model/base.go @@ -4,9 +4,18 @@ import ( "bytes" "strings" + "golang.org/x/text/cases" + "golang.org/x/text/language" "gorm.io/gen/field" ) +// TitleCase converts a string to title case using golang.org/x/text/cases +// This replaces the deprecated strings.Title function +func TitleCase(s string) string { + caser := cases.Title(language.English) + return caser.String(s) +} + const ( // DefaultModelPkg ... DefaultModelPkg = "model" @@ -203,13 +212,13 @@ func (m *Field) GenType() string { typ := strings.TrimLeft(m.Type, "*") switch typ { case "string", "bytes": - return strings.Title(typ) + return TitleCase(typ) case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64": - return strings.Title(typ) + return TitleCase(typ) case "float64", "float32": - return strings.Title(typ) + return TitleCase(typ) case "bool": - return strings.Title(typ) + return TitleCase(typ) case "time.Time": return "Time" case "json.RawMessage", "[]byte": diff --git a/internal/template/model.go b/internal/template/model.go index 7c594158..a1838e7d 100644 --- a/internal/template/model.go +++ b/internal/template/model.go @@ -29,6 +29,12 @@ type {{.ModelStructName}} struct { `{{end}} } +{{if .WithAutoRegistry}} +func init() { + RegisterModel(&{{.ModelStructName}}{}, TableName{{.ModelStructName}}) +} +{{end}} + ` // ModelMethod model struct DIY method diff --git a/internal/template/registry.go b/internal/template/registry.go new file mode 100644 index 00000000..f1609987 --- /dev/null +++ b/internal/template/registry.go @@ -0,0 +1,78 @@ +package template + +// ModelRegistry template for generating model registry file +const ModelRegistry = NotEditMark + ` +package {{.Package}} + +import ( + "reflect" + "sync" +) + +// ModelRegistry holds information about registered models in this package +type ModelRegistry struct { + mu sync.RWMutex + models []interface{} + names []string +} + +// registry is the package-level model registry +var registry = &ModelRegistry{ + models: make([]interface{}, 0), + names: make([]string, 0), +} + +// RegisterModel registers a model to the package registry +func RegisterModel(model interface{}, name string) { + registry.RegisterModel(model, name) +} + +// RegisterModel registers a model to this registry +func (r *ModelRegistry) RegisterModel(model interface{}, name string) { + r.mu.Lock() + defer r.mu.Unlock() + + // Check if already registered + for i, existing := range r.models { + if reflect.TypeOf(existing) == reflect.TypeOf(model) { + r.names[i] = name // Update name if type already exists + return + } + } + + r.models = append(r.models, model) + r.names = append(r.names, name) +} + +// GetAllModels returns all registered models in this package +func GetAllModels() []interface{} { + return registry.GetAllModels() +} + +// GetAllModels returns all registered models +func (r *ModelRegistry) GetAllModels() []interface{} { + r.mu.RLock() + defer r.mu.RUnlock() + + result := make([]interface{}, len(r.models)) + copy(result, r.models) + return result +} + +// GetAllModelNames returns all registered model names in this package +func GetAllModelNames() []string { + return registry.GetAllModelNames() +} + +// GetAllModelNames returns all registered model names +func (r *ModelRegistry) GetAllModelNames() []string { + r.mu.RLock() + defer r.mu.RUnlock() + + result := make([]string, len(r.names)) + copy(result, r.names) + return result +} + + +`