gormcnm - Eliminate hardcoded strings in GORM operations with type-safe column names and compile-time validation.
| Language | ORM | Type-Safe Columns | Example |
|---|---|---|---|
| Java | MyBatis Plus | Example::getName |
wrapper.eq(Example::getName, "alice") |
| Python | SQLAlchemy | Example.name |
query.filter(Example.name == "alice") |
| Go | GORMCNM | cls.Name.Eq() |
db.Where(cls.Name.Eq("alice")) |
- π― Core Value: Avoid hardcoded column names with type-safe operations
- π― Type-Safe Column Operations: Generic
ColumnName[T]type with compile-time validation - β‘ Zero Runtime Overhead: Type checking happens at compile time
- π Rename-Safe Queries: IDE auto-completion and automatic rename support
- π Rich Gorm Operations: Comprehensive comparison, range, pattern, and aggregate operations
- π Ecosystem Foundation: Powers code generation and repo pattern tools
go get github.com/yyle88/gormcnmpackage main
import (
"fmt"
"github.com/google/uuid"
"github.com/yyle88/done"
"github.com/yyle88/gormcnm"
"github.com/yyle88/neatjson/neatjsons"
"github.com/yyle88/rese"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type Account struct {
Username string `gorm:"primary_key;type:varchar(100);"`
Nickname string `gorm:"column:nickname;"`
Age int `gorm:"column:age;"`
}
const (
columnUsername = gormcnm.ColumnName[string]("username")
columnNickname = gormcnm.ColumnName[string]("nickname")
columnAge = gormcnm.ColumnName[int]("age")
)
func main() {
dsn := fmt.Sprintf("file:db-%s?mode=memory&cache=shared", uuid.New().String())
db := rese.P1(gorm.Open(sqlite.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
}))
defer rese.F0(rese.P1(db.DB()).Close)
//CREATE TABLE `accounts` (`username` varchar(100),`nickname` text,`age` integer,PRIMARY KEY (`username`))
done.Done(db.AutoMigrate(&Account{}))
//INSERT INTO `accounts` (`username`,`nickname`,`age`) VALUES ("alice","Alice",17)
done.Done(db.Create(&Account{Username: "alice", Nickname: "Alice", Age: 17}).Error)
//SELECT * FROM `accounts` WHERE username="alice" ORDER BY `accounts`.`username` LIMIT 1
var account Account
done.Done(db.Where(columnUsername.Eq("alice")).First(&account).Error)
fmt.Println(neatjsons.S(account))
//UPDATE `accounts` SET `nickname`="Alice-2" WHERE `username` = "alice"
done.Done(db.Model(&account).Update(columnNickname.Kv("Alice-2")).Error)
//SELECT * FROM `accounts` WHERE username="alice" ORDER BY `accounts`.`username` LIMIT 1
done.Done(db.Where(columnUsername.Eq("alice")).First(&account).Error)
fmt.Println(neatjsons.S(account))
//UPDATE `accounts` SET `age`=18,`nickname`="Alice-3" WHERE `username` = "alice"
done.Done(db.Model(&account).Updates(columnNickname.Kw("Alice-3").Kw(columnAge.Kv(18)).AsMap()).Error)
//SELECT * FROM `accounts` WHERE username="alice" ORDER BY `accounts`.`username` LIMIT 1
done.Done(db.Where(columnUsername.Eq("alice")).First(&account).Error)
fmt.Println(neatjsons.S(account))
//UPDATE `accounts` SET `age`=age + 1 WHERE `username` = "alice"
done.Done(db.Model(&account).Update(columnAge.KeAdd(1)).Error)
//SELECT * FROM `accounts` WHERE username="alice" ORDER BY `accounts`.`username` LIMIT 1
done.Done(db.Where(columnUsername.Eq("alice")).First(&account).Error)
fmt.Println(neatjsons.S(account))
}β¬οΈ Source: Source
package main
import (
"fmt"
"github.com/google/uuid"
"github.com/yyle88/gormcnm"
"github.com/yyle88/must"
"github.com/yyle88/rese"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// Example is a gorm model define 3 fields(name, type, rank)
type Example struct {
Name string `gorm:"primary_key;type:varchar(100);"`
Type string `gorm:"column:type;"`
Rank int `gorm:"column:rank;"`
}
// Now define the fields enum vars(name, type rank)
const (
columnName = gormcnm.ColumnName[string]("name")
columnType = gormcnm.ColumnName[string]("type")
columnRank = gormcnm.ColumnName[int]("rank")
)
func main() {
//new db connection
dsn := fmt.Sprintf("file:db-%s?mode=memory&cache=shared", uuid.New().String())
db := rese.P1(gorm.Open(sqlite.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
}))
defer rese.F0(rese.P1(db.DB()).Close)
//create example data
must.Done(db.AutoMigrate(&Example{}).Error)
must.Done(db.Save(&Example{Name: "abc", Type: "xyz", Rank: 123}).Error)
must.Done(db.Save(&Example{Name: "aaa", Type: "xxx", Rank: 456}).Error)
{
//SELECT * FROM `examples` WHERE name="abc" ORDER BY `examples`.`name` LIMIT 1
var res Example
must.Done(db.Where("name=?", "abc").First(&res).Error)
fmt.Println(res)
}
{
//SELECT * FROM `examples` WHERE name="abc" AND type="xyz" AND rank>100 AND rank<200 ORDER BY `examples`.`name` LIMIT 1
var res Example
must.Done(db.Where(columnName.Eq("abc")).
Where(columnType.Eq("xyz")).
Where(columnRank.Gt(100)).
Where(columnRank.Lt(200)).
First(&res).Error)
fmt.Println(res)
}
}β¬οΈ Source: Source
The ColumnName[T] generic type provides type-safe SQL operations:
type ColumnName[T any] stringdb.Where(columnAge.Eq(25)) // =
db.Where(columnAge.Ne(25)) // !=
db.Where(columnAge.Gt(18)) // >
db.Where(columnAge.Gte(18)) // >=
db.Where(columnAge.Lt(65)) // <
db.Where(columnAge.Lte(65)) // <=| Method | SQL | Example |
|---|---|---|
Between(a, b) |
BETWEEN a AND b |
cls.Age.Between(18, 65) |
In(values) |
IN (...) |
cls.ID.In([]int{1,2,3}) |
Like(pattern) |
LIKE pattern |
cls.Name.Like("A%") |
IsNull() |
IS NULL |
cls.DeletedAt.IsNull() |
IsNotNull() |
IS NOT NULL |
cls.Email.IsNotNull() |
| Method | Description | Example |
|---|---|---|
Kv(value) |
Single field update | db.Model(&user).Update(cls.Age.Kv(26)) |
Kw(value) |
Build update map | cls.Age.Kw(26).Kw(cls.Email.Kv("new@example.com")).AsMap() |
KeAdd(n) |
Expression: add | db.Model(&user).Update(cls.Age.KeAdd(1)) |
KeSub(n) |
Expression: subtract | db.Model(&user).Update(cls.Score.KeSub(10)) |
ColumnValueMap is used when updating multiple columns with gormrepo.Updates, gormrepo.UpdatesM, or GORM native db.Updates.
With gormrepo.Updates (requires AsMap() conversion):
repo.Updates(where, func(cls *AccountColumns) map[string]interface{} {
return cls.
Kw(cls.Nickname.Kv(newNickname)).
Kw(cls.Password.Kv(newPassword)).
AsMap() // Convert to map[string]interface{}
})With gormrepo.UpdatesM (no AsMap() needed, recommended):
repo.UpdatesM(where, func(cls *AccountColumns) gormcnm.ColumnValueMap {
return cls.
Kw(cls.Nickname.Kv(newNickname)).
Kw(cls.Password.Kv(newPassword))
// No AsMap() needed!
})With GORM native db.Updates:
db.Model(&account).Updates(
cls.Nickname.Kw(newNickname).
Kw(cls.Password.Kv(newPassword)).
AsMap(),
)| Method | SQL | Example |
|---|---|---|
Count(alias) |
COUNT(column) AS alias |
db.Select(cls.ID.Count("total")) |
Ob(direction) |
ORDER BY |
db.Order(cls.Age.Ob("asc").Ox()) |
Combine gormcnm with gormrepo to get type-safe CRUD operations.
// Create repo with columns
repo := gormrepo.NewRepo(&Account{}, (&Account{}).Columns())
// Concise approach with gormrepo/gormclass
repo := gormrepo.NewRepo(gormclass.Use(&Account{}))
// Type-safe queries
account, err := repo.With(ctx, db).First(func(db *gorm.DB, cls *AccountColumns) *gorm.DB {
return db.Where(cls.Username.Eq("alice"))
})
// Find with conditions
accounts, err := repo.With(ctx, db).Find(func(db *gorm.DB, cls *AccountColumns) *gorm.DB {
return db.Where(cls.Age.Gte(18)).Where(cls.Age.Lte(65))
})
// Type-safe updates
err := repo.With(ctx, db).Updates(
func(db *gorm.DB, cls *AccountColumns) *gorm.DB {
return db.Where(cls.ID.Eq(1))
},
func(cls *AccountColumns) map[string]interface{} {
return cls.Kw(cls.Age.Kv(26)).Kw(cls.Nickname.Kv("NewNick")).AsMap()
},
)π See gormrepo to get complete documentation and more examples.
This package includes extension sub-packages for specialized database operations:
- π¦ gormcnmjson - Type-safe JSON column operations (SQLite JSON functions support)
Future Extensions (planned):
| Package | Purpose | Status |
|---|---|---|
| gormcnmtext | Text search operations | Planned |
| gormcnmdate | Date/time operations | Planned |
| gormcnmmath | Math operations | Planned |
Explore the complete GORM ecosystem with these integrated packages:
- gormcnm - GORM foundation providing type-safe column operations and statement construction (this project)
- gormcngen - Code generation tool using AST, enables type-safe GORM operations
- gormrepo - Repo pattern implementation with GORM best practices
- gormmom - Native language GORM tag generation engine with smart column naming
- gormzhcn - Complete Chinese programming interface with GORM
Each package targets different aspects of GORM development, from localization to type-safe operations and code generation.
MIT License - see LICENSE.
Contributions are welcome! Report bugs, suggest features, and contribute code:
- π Mistake reports? Open an issue on GitHub with reproduction steps
- π‘ Fresh ideas? Create an issue to discuss
- π Documentation confusing? Report it so we can improve
- π Need new features? Share the use cases to help us understand requirements
- β‘ Performance issue? Help us optimize through reporting slow operations
- π§ Configuration problem? Ask questions about complex setups
- π’ Follow project progress? Watch the repo to get new releases and features
- π Success stories? Share how this package improved the workflow
- π¬ Feedback? We welcome suggestions and comments
New code contributions, follow this process:
- Fork: Fork the repo on GitHub (using the webpage UI).
- Clone: Clone the forked project (
git clone https://github.com/yourname/repo-name.git). - Navigate: Navigate to the cloned project (
cd repo-name) - Branch: Create a feature branch (
git checkout -b feature/xxx). - Code: Implement the changes with comprehensive tests
- Testing: (Golang project) Ensure tests pass (
go test ./...) and follow Go code style conventions - Documentation: Update documentation to support client-facing changes
- Stage: Stage changes (
git add .) - Commit: Commit changes (
git commit -m "Add feature xxx") ensuring backward compatible code - Push: Push to the branch (
git push origin feature/xxx). - PR: Open a merge request on GitHub (on the GitHub webpage) with detailed description.
Please ensure tests pass and include relevant documentation updates.
Welcome to contribute to this project via submitting merge requests and reporting issues.
Project Support:
- β Give GitHub stars if this project helps you
- π€ Share with teammates and (golang) programming friends
- π Write tech blogs about development tools and workflows - we provide content writing support
- π Join the ecosystem - committed to supporting open source and the (golang) development scene
Have Fun Coding with this package! πππ