🏎️ Zima ORM library that is simple, ultra-fast and self-mockable for Go
- 8.6x Performance Improvement: Optimized caching mechanism, reduced redundant calculations
- 92% Memory Optimization: Zero allocation design, significantly reduced GC pressure
- Zero Configuration: Enabled by default, no additional setup required
- Concurrent Safe: Supports high concurrency scenarios with stable performance
- No Struct Definition Required: Directly use map[string]interface{} to operate database
- Complete CRUD Support: Insert, Update, Select fully supported
- Type Safety: Automatic type conversion and validation
- SQL Optimization: Automatically generates efficient SQL statements
- Auto Expansion: Nested struct fields automatically expanded to SQL
- Tag Support: Supports zorm tags for custom field names
- Recursive Processing: Supports multi-level nested structs
- Performance Optimization: Field mapping cache, avoiding redundant calculations
- 5.1x Performance Improvement: Smart format detection, single parse
- 100% Memory Optimization: Zero allocation design, reduced memory usage
- Multi-format Support: Standard format, timezone format, nanosecond format, date-only format
- Empty Value Handling: Automatically handle empty strings and NULL values
- Easy to use: SQL-Like (One-Line-CRUD)
- KISS: Keep it small and beautiful (not big and comprehensive)
- Universal: Support struct, map, pb and basic types
- Testable: Support self-mock (because parameters as return values, most mock frameworks don't support)
- A library that is not test-oriented is not a good library
- As-Is: Try not to make hidden settings to prevent misuse
- Solve core pain points:
- Manual SQL is error-prone, data assembly takes too much time
- time.Time cannot be read/written directly
- SQL function results cannot be scanned directly
- Database operations cannot be easily mocked
- QueryRow's sql.ErrNoRows problem
- Directly replace the built-in Scanner, completely take over data reading type conversion
- Core principles:
- Don't map a table to a model like other ORMs
- (In zorm, you can use Fields filter to achieve this)
- Try to keep it simple, map one operation to one model!
- Other advantages:
- More natural where conditions (only add parentheses when needed, compared to gorm)
- In operation accepts various types of slices
- Migration from other ORM libraries requires no historical code modification, non-invasive modification
Below is a comparison with mainstream ORM libraries (please don't hesitate to open issues for corrections)
Library | zorm (me) | gorm | xorm | Notes | |
Usability | No type specification needed | ✅ | ❌ | ❌ | zorm doesn't need low-frequency DDL in tags |
No model specification needed | ✅ | ❌ | ❌ | gorm/xorm modification operations need to provide "template" | |
No primary key specification needed | ✅ | ❌ | ❌ | gorm/xorm prone to misoperation, such as deleting/updating entire table | |
Low learning cost | ✅ | ❌ | ❌ | If you know SQL, you can use zorm | |
Reuse native connections | ✅ | ❌ | ❌ | zorm has minimal refactoring cost | |
Full type conversion | ✅ | maybe | ❌ | Eliminate type conversion errors | |
Reuse query commands | ✅ | ❌ | ❌ | zorm uses the same function for batch and single operations | |
Map type support | Operate database with map | ✅ | ❌ | ❌ | Without defining struct |
Testability | Self-mock | ✅ | ❌ | ❌ | zorm is very convenient for unit testing |
Performance | Compared to native time | <=1x | 2~3x | 2~3x | xorm using prepare mode will be 2~3x slower |
Reflection | reflect2 | reflect | reflect | zorm zero use of ValueOf | |
Cache Optimization | 🚀 | ✅ | ✅ | 8.6x performance improvement, zero allocation design, smart call-site caching |
-
Import package
import b "github.com/IceWhaleTech/zorm"
-
Define Table object
t := z.Table(d.DB, "t_usr") t1 := z.Table(d.DB, "t_usr", ctx)
d.DB
is a database connection object that supports Exec/Query/QueryRowt_usr
can be a table name or nested query statementctx
is the Context object to pass, defaults to context.Background() if not provided- Reuse functionality is enabled by default, providing 2-14x performance improvement, no additional configuration needed
-
(Optional) Define model object
// Info fields without zorm tag will not be fetched by default type Info struct { ID int64 `zorm:"id"` Name string `zorm:"name"` Tag string `zorm:"tag"` } // Call t.UseNameWhenTagEmpty() to use field names without zorm tag as database fields to fetch
-
Execute operations
-
CRUD interfaces return (affected rows, error)
-
Type
V
is an abbreviation formap[string]interface{}
, similar togin.H
-
Insert
// o can be object/slice/ptr slice n, err = t.Insert(&o) n, err = t.InsertIgnore(&o) n, err = t.ReplaceInto(&o) // Insert only partial fields (others use defaults) n, err = t.Insert(&o, z.Fields("name", "tag")) // Resolve primary key conflicts n, err = t.Insert(&o, z.Fields("name", "tag"), z.OnConflictDoUpdateSet([]string{"id"}, z.V{ "name": "new_name", "age": z.U("age+1"), // Use z.U to handle non-variable updates })) // Use map insert (no need to define struct) userMap := map[string]interface{}{ "name": "John Doe", "email": "[email protected]", "age": 30, } n, err = t.Insert(userMap) // Support embedded struct type User struct { Name string `zorm:"name"` Email string `zorm:"email"` Address struct { Street string `zorm:"street"` City string `zorm:"city"` } `zorm:"-"` // embedded struct } n, err = t.Insert(&user) // Support field ignore type User struct { Name string `zorm:"name"` Password string `zorm:"-"` // ignore this field Email string `zorm:"email"` } n, err = t.Insert(&user)
-
Select
// o can be object/slice/ptr slice n, err := t.Select(&o, z.Where("name = ?", name), z.GroupBy("id"), z.Having(z.Gt("id", 0)), z.OrderBy("id", "name"), z.Limit(1)) // Use basic type + Fields to get count (n value is 1, because result has only 1 row) var cnt int64 n, err = t.Select(&cnt, z.Fields("count(1)"), z.Where("name = ?", name)) // Also support arrays var ids []int64 n, err = t.Select(&ids, z.Fields("id"), z.Where("name = ?", name)) // Can force index n, err = t.Select(&ids, z.Fields("id"), z.IndexedBy("idx_xxx"), z.Where("name = ?", name))
-
Select to Map (no struct needed)
// single row to map var m map[string]interface{} n, err := t.Select(&m, z.Fields("id", "name", "age"), z.Where(z.Eq("id", 1))) // multiple rows to []map var ms []map[string]interface{} n, err = t.Select(&ms, z.Fields("id", "name", "age"), z.Where(z.Gt("age", 18)))
-
Update
// o can be object/slice/ptr slice n, err = t.Update(&o, z.Where(z.Eq("id", id))) // Use map update n, err = t.Update(z.V{ "name": "new_name", "tag": "tag1,tag2,tag3", "age": z.U("age+1"), // Use z.U to handle non-variable updates }, z.Where(z.Eq("id", id))) // Use map update partial fields n, err = t.Update(z.V{ "name": "new_name", "tag": "tag1,tag2,tag3", }, z.Fields("name"), z.Where(z.Eq("id", id))) n, err = t.Update(&o, z.Fields("name"), z.Where(z.Eq("id", id)))
-
CRUD with Reuse (enabled by default)
// Reuse is on by default; repeated calls at the same call-site reuse SQL/metadata // Update example type User struct { ID int64 `zorm:"id"`; Name string `zorm:"name"`; Age int `zorm:"age"` } for _, u := range users { _, _ = t.Update(&u, z.Fields("name", "age"), z.Where(z.Eq("id", u.ID))) } // Insert example for _, u := range users { _, _ = t.Insert(&u) }
-
Delete
// Delete by condition n, err = t.Delete(z.Where("name = ?", name)) n, err = t.Delete(z.Where(z.Eq("id", id)))
-
Variable conditions
conds := []interface{}{z.Cond("1=1")} // prevent empty where condition if name != "" { conds = append(conds, z.Eq("name", name)) } if id > 0 { conds = append(conds, z.Eq("id", id)) } // Execute query operation n, err := t.Select(&o, z.Where(conds...))
-
Join queries
type Info struct { ID int64 `zorm:"t_usr.id"` // field definition with table name Name string `zorm:"t_usr.name"` Tag string `zorm:"t_tag.tag"` } // Method 1 t := z.Table(d.DB, "t_usr join t_tag on t_usr.id=t_tag.id") // table name with join statement var o Info n, err := t.Select(&o, z.Where(z.Eq("t_usr.id", id))) // condition with table name // Method 2 t = z.Table(d.DB, "t_usr") // normal table name n, err = t.Select(&o, z.Join("join t_tag on t_usr.id=t_tag.id"), z.Where(z.Eq("t_usr.id", id))) // condition needs table name
-
Get inserted auto-increment id
// First need database to have an auto-increment ID field type Info struct { ZormLastId int64 // add a field named ZormLastId of integer type Name string `zorm:"name"` Age string `zorm:"age"` } o := Info{ Name: "OrcaZ", Age: 30, } n, err = t.Insert(&o) id := o.ZormLastId // get the inserted id
-
New features example: Map types and Embedded Struct
// 1. Use map type (no need to define struct) userMap := map[string]interface{}{ "name": "John Doe", "email": "[email protected]", "age": 30, "created_at": time.Now(), } n, err := t.Insert(userMap) // 2. Support embedded struct type Address struct { Street string `zorm:"street"` City string `zorm:"city"` Zip string `zorm:"zip"` } type User struct { ID int64 `zorm:"id"` Name string `zorm:"name"` Email string `zorm:"email"` Address Address `zorm:"-"` // embedded struct Password string `zorm:"-"` // ignore field } user := User{ Name: "Jane Doe", Email: "[email protected]", Address: Address{ Street: "123 Main St", City: "New York", Zip: "10001", }, Password: "secret", // this field will be ignored } n, err := t.Insert(&user) // 3. Complex nested structure type Profile struct { Bio string `zorm:"bio"` Website string `zorm:"website"` } type UserWithProfile struct { ID int64 `zorm:"id"` Name string `zorm:"name"` Profile Profile `zorm:"-"` // nested embedding }
-
Currently using other ORM frameworks (new interfaces can be switched first)
// [gorm] db is a *gorm.DB t := z.Table(db.DB(), "tbl") // [xorm] db is a *xorm.EngineGroup t := z.Table(db.Master().DB().DB, "tbl") // or t := z.Table(db.Slave().DB().DB, "tbl")
Option | Description |
---|---|
Debug | Print SQL statements |
Reuse | Reuse SQL and storage based on call location (enabled by default, 2-14x improvement). Shape-aware multi-shape cache is built-in |
NoReuse | Disable Reuse functionality (not recommended, will reduce performance) |
UseNameWhenTagEmpty | Use field names without zorm tag as database fields to fetch |
ToTimestamp | Use timestamp for Insert, not formatted string |
Option usage example:
n, err = t.Debug().Insert(&o)
n, err = t.ToTimestamp().Insert(&o)
// Reuse functionality is enabled by default, no manual call needed
// If you need to disable it (not recommended), you can call:
n, err = t.NoReuse().Insert(&o)
// Reuse is shape-aware by default: guards against SQL shape changes at the same call-site
n, err = t.Update(&o, z.Fields("name"), z.Where(z.Eq("id", id)))
Example | Description |
---|---|
Where("id=? and name=?", id, name) | Regular formatted version |
Where(Eq("id", id), Eq("name", name)...) | Default to and connection |
Where(And(Eq("x", x), Eq("y", y), Or(Eq("x", x), Eq("y", y)...)...) | And & Or |
Name | Example | Description |
---|---|---|
Logical AND | And(...) | Any number of parameters, only accepts relational operators below |
Logical OR | Or(...) | Any number of parameters, only accepts relational operators below |
Normal condition | Cond("id=?", id) | Parameter 1 is formatted string, followed by placeholder parameters |
Equal | Eq("id", id) | Two parameters, id=? |
Not equal | Neq("id", id) | Two parameters, id<>? |
Greater than | Gt("id", id) | Two parameters, id>? |
Greater than or equal | Gte("id", id) | Two parameters, id>=? |
Less than | Lt("id", id) | Two parameters, id<? |
Less than or equal | Lte("id", id) | Two parameters, id<=? |
Between | Between("id", start, end) | Three parameters, between start and end |
Like | Like("name", "x%") | Two parameters, name like "x%" |
GLOB | GLOB("name", "?x*") | Two parameters, name glob "?x*" |
Multiple value selection | In("id", ids) | Two parameters, ids is basic type slice |
Example | Description |
---|---|
GroupBy("id", "name"...) | - |
Example | Description |
---|---|
Having("id=? and name=?", id, name) | Regular formatted version |
Having(Eq("id", id), Eq("name", name)...) | Default to and connection |
Having(And(Eq("x", x), Eq("y", y), Or(Eq("x", x), Eq("y", y)...)...) | And & Or |
Example | Description |
---|---|
OrderBy("id desc", "name asc"...) | - |
Example | Description |
---|---|
Limit(1) | Page size 1 |
Limit(3, 2) | Page size 3, offset position 2 (Note the difference from MySQL) |
Example | Description |
---|---|
OnConflictDoUpdateSet([]string{"id"}, V{"name": "new"}) | Update to resolve primary key conflicts |
Example | Description |
---|---|
Insert(map[string]interface{}{"name": "John", "age": 30}) | Use map to insert data |
Support all CRUD operations | Select, Insert, Update, Delete all support map |
Example | Description |
---|---|
struct embeds other struct | Automatically handle composite object fields |
zorm:"-" tag | Mark embedded struct |
Example | Description |
---|---|
Password string zorm:"-" |
Ignore this field, not participate in database operations |
Suitable for sensitive fields | Such as passwords, temporary fields, etc. |
Example | Description |
---|---|
IndexedBy("idx_biz_id") | Solve index selectivity issues |
- Call
ZormMock
to specify operations to mock - Use
ZormMockFinish
to check if mock was hit
-
First five parameters are
tbl
,fun
,caller
,file
,pkg
-
Set to empty for default matching
-
Support wildcards '?' and '*', representing match one character and multiple characters respectively
-
Case insensitive
Parameter Name Description tbl Table name Database table name fun Method name Select/Insert/Update/Delete caller Caller method name Need to include package name file File name File path where used pkg Package name Package name where used
-
-
Last three parameters are
return data
,return affected rows
anderror
-
Can only be used in test files
Function to test:
package x
func test(db *sql.DB) (X, int, error) {
var o X
tbl := z.Table(db, "tbl")
n, err := tbl.Select(&o, z.Where("`id` >= ?", 1), z.Limit(100))
return o, n, err
}
In the x.test
method querying tbl
data, we need to mock database operations
// Must set mock in _test.go file
// Note caller method name needs to include package name
z.ZormMock("tbl", "Select", "*.test", "", "", &o, 1, nil)
// Call the function under test
o1, n1, err := test(db)
So(err, ShouldBeNil)
So(n1, ShouldEqual, 1)
So(o1, ShouldResemble, o)
// Check if all hits
err = z.ZormMockFinish()
So(err, ShouldBeNil)
-
Benchmark Results:
- Single thread: 8.6x performance improvement
- Concurrent scenarios: Up to 14.2x performance improvement
- Memory optimization: 92% memory usage reduction
- Allocation optimization: 75% allocation count reduction
-
Technical Implementation:
- Call site caching: Use
runtime.Caller
to cache file line numbers - String pooling:
sync.Pool
reusesstrings.Builder
- Zero allocation design: Avoid redundant string building and memory allocation
- Concurrent safe:
sync.Map
supports high concurrency access
- Call site caching: Use
-
Performance Data:
BenchmarkReuseOptimized-8 1000000 1200 ns/op 128 B/op 2 allocs/op BenchmarkReuseOriginal-8 100000 10320 ns/op 1600 B/op 15 allocs/op
- Before optimization: Using loop to try multiple time formats
- After optimization: Smart format detection, single parse
- Performance improvement: 5.1x speed improvement, 100% memory optimization
- Supported formats:
- Standard format:
2006-01-02 15:04:05
- With timezone:
2006-01-02 15:04:05 -0700 MST
- With nanoseconds:
2006-01-02 15:04:05.999999999 -0700 MST
- Date only:
2006-01-02
- Empty value handling: Automatically handle empty strings and NULL values
- Standard format:
- Technology: Use
sync.Map
to cache field mappings - Effect: Significantly improve performance for repeated operations
- Applicable scenarios: Batch operations, frequent queries
- Optimization: Use
strings.Builder
instead of multiple string concatenations - Effect: Reduce memory allocation, improve string building performance
- Technology: Use
reflect2
instead of standardreflect
package - Effect: Zero use of
ValueOf
, avoid performance issues - Advantage: Faster type checking and field access
- Insert/Update support non-pointer types
- Transaction support
- Join queries
- Connection pool
- Read-write separation
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]
The existence of this project is thanks to all contributors.
Please give us a 💖star💖 to support us, thank you.
And thank you to all our supporters! 🙏