- Notifications
You must be signed in to change notification settings - Fork7
KORM, an elegant and lightning-fast ORM for all your concurrent and async needs. Inspired by the highly popular Django Framework, KORM offers similar functionality with the added bonus of performance
License
kamalshkeir/korm
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Introducing Korm - the elegant, lightning-fast ORM/Framework for all your needs, seebenchmarks. Inspired by the highly popular Django Framework, Korm offers similar functionality with the added bonus of performance
It is also composable, allowing for integration with a network websocket PubSub usingWithBus when you want to synchronise your data between multiple Korm orWithDashboard to have a complete setup of server bus and Admin Dashboard.
- Django become very hard to work with when you need concurrency and async, you will need django channels and a server like daphne or uvicorn, Go have the perfect implementation
- Django can handle at most 300 request per second, Go handle 44,000 requests per second (benchmarks done on my machine)
- The API is also more user-friendly and less verbose than Django's
- Deploying an executable binary file using Korm , with automatic TLS Let's encrypt, a built-in Admin Dashboard, Interactive Shell, Eventbus to communicate between multiple Korm applications is pretty neat
- Additionally, its caching system uses goroutines and channels to efficiently to clean the cache when rows or tables are created, updated, deleted, or dropped
New: When using korm.WithDashboard, now you have access to all logs in realtime (websockets) from admin dashboard when you log using lg pkg. By default only 10 last logs are keeped in memory, you can increase it using lg.SaveLogs(50) for keeping last 50 logs
New: Automatic check your structs (schema) against database tables, prompt you with changes, and so it can add or remove columns by adding or removing fields to the struct, it is Disabled by default, use
korm.EnableCheck()to enable itNew:Handle Nested or Embeded structs and slice of structs through joins, like sqlx, but sqlx doesn't handle slice of structs
New: korm.QueryNamed, QueryNamedS, korm.ExecNamed, korm.ExecContextNamed and WhereNamed(query string, args map[string]any) like :Where("email = :email",map[string]any{"email":"abc@mail.com"})
New: korm.LogsQueries() that log statements and time tooked by sql queries
PPROF Go profiling tool andMetrics Prometheus
Admin dashboard with ready offline and installable PWA (using /static/sw.js and /static/manifest.webmanifest). All statics mentionned in
sw.jswill be cached and served by the service worker, you can inspect the Network Tab in the browser to check itShared Network Bus allowing you to send and recv data in realtime using pubsub websockets between your ORMs, so you can decide how you data will be distributed between different databases, seeExample
Built-in Authentication using
korm.Auth,korm.Adminorkorm.BasicAuthmiddlewares, whenever Auth and Admin middlewares are used, you get access to the.Usermodel and variable.IsAuthenticatedfrom any template html like this exampleadmin_nav.htmlInteractive Shell, to CRUD in your databases from command line, use
korm.WithShell()AutoMigrate directly from struct
Compatible with official database/sql, so you can do your queries yourself using sql.DB `korm.GetConnection()``, and overall a painless integration of your existing codebases using database/sql
Router/Mux accessible from the serverBus after calling
korm.WithBus(...opts)orkorm.WithDashboard(addr, ...opts)Hooks : OnInsert OnSet OnDelete and OnDrop
many to many relationships
GENERATED ALWAYS AS tag added (all dialects)
Concatination and Length support for
Whereand for tags:checkandgenerated(all dialects)Support for foreign keys, indexes , checks,...See all
Kenv load env vars to struct
Python Bus Client
pip install ksbus
- Sqlite
- Mysql
- Maria
- Postgres
- Cockroach
go get -u github.com/kamalshkeir/korm@latest
go get -u github.com/kamalshkeir/sqlitedriver@latestgo get -u github.com/kamalshkeir/pgdriver@latestgo get -u github.com/kamalshkeir/mysqldriver@latest
// Debug when true show extra useful logs for queries executed for migrations and queries statementsDebug=false// FlushCacheEvery execute korm.FlushCache() every 10 min by default, you should not worry about it, but useful that you can change itFlushCacheEvery=10*time.Minute// SetCacheMaxMemory set max size of each cache cacheAllS AllM ...korm.SetCacheMaxMemory(megaByteint)// default maximum of 50 Mb , cannot be lower// Connection poolMaxOpenConns=20MaxIdleConns=20MaxLifetime=30*time.MinuteMaxIdleTime=15*time.Minute
// sqlite// go get github.com/kamalshkeir/sqlitedrivererr:=korm.New(korm.SQLITE,"dbName",sqlitedriver.Use())// Connect// postgres, cockroach// go get github.com/kamalshkeir/pgdrivererr:=korm.New(korm.POSTGRES,"dbName",pgdriver.Use(),"user:password@localhost:5432")// Connect// mysql, maria// go get github.com/kamalshkeir/mysqldrivererr:=korm.New(korm.MYSQL,"dbName",mysqldriver.Use(),"user:password@localhost:3306")// Connectkorm.Shutdown(databasesName...string)error
package mainimport ("fmt""time""github.com/kamalshkeir/lg""github.com/kamalshkeir/korm""github.com/kamalshkeir/sqlitedriver")typeClassstruct {Iduint`korm:"pk"`NamestringStudents []Student}typeStudentstruct {Iduint`korm:"pk"`NamestringClassuint`korm:"fk:classes.id:cascade:cascade"`ClassesClass}funcmain() {err:=korm.New(korm.SQLITE,"db",sqlitedriver.Use())iflg.CheckError(err) {return}deferkorm.Shutdown()server:=korm.WithDashboard(":9313")korm.WithShell()err= korm.AutoMigrate[Class]("classes")lg.CheckError(err)err= korm.AutoMigrate[Student]("students")lg.CheckError(err)// go run main.go shell to createsuperuser// connect to admin and create some data to query// nested structs with joins, scan the result to the channel directly after each row// so instead of receiving a slice, you will receive data on the channel[0] of the passed slicestudentsChan:= []chanStudent{make(chanStudent)}gofunc() {fors:=rangestudentsChan[0] {fmt.Println("chan students:",s)}}()err=korm.To(&studentsChan).Query("select students.*,classes.id as 'classes.id',classes.name as 'classes.name' from students join classes where classes.id = students.class")lg.CheckError(err)fmt.Println()// Named with nested (second argument of 'To') filled automatically from join, support nested slices and structsclasses:= []Class{}query:="select classes.*, students.id as 'students.id',students.name as 'students.name' from classes join students on students.class = classes.id order by :order_here"err=korm.To(&classes,true).Named(query,map[string]any{"order_here":"classes.id",})lg.CheckError(err)for_,s:=rangeclasses {fmt.Println("class:",s)}fmt.Println()// // not nested, only remove second arg true from 'To' methodstudents:= []Student{}err=korm.To(&students,true).Query("select students.*,classes.id as 'classes.id',classes.name as 'classes.name' from students join classes where classes.id = students.class")lg.CheckError(err)for_,s:=rangestudents {fmt.Println("student:",s)}fmt.Println()maps:= []map[string]any{}err=korm.To(&maps).Query("select * from students")lg.CheckError(err)fmt.Println("maps =",maps)fmt.Println()names:= []*string{}err=korm.To(&names).Query("select name from students")lg.CheckError(err)fmt.Println("names =",names)fmt.Println()ids:= []int{}err=korm.To(&ids).Query("select id from students")lg.CheckError(err)fmt.Println("ids =",ids)fmt.Println()bools:= []bool{}err=korm.To(&bools).Query("select is_admin from users")lg.CheckError(err)fmt.Println("bools =",bools)fmt.Println()times:= []time.Time{}err=korm.To(×).Query("select created_at from users")lg.CheckError(err)fmt.Println("times =",times)server.Run()}// OUTPUT// chan students: {1 student-1 1 {1 Math []}}// chan students: {2 student-2 2 {2 French []}}// chan students: {3 student-3 1 {1 Math []}}// chan students: {4 student-4 2 {2 French []}}// class: {1 Math [{1 student-1 0 {0 []}} {3 student-3 0 {0 []}}]}// class: {2 French [{2 student-2 0 {0 []}} {4 student-4 0 {0 []}}]}// student: &{1 student-1 1 {1 Math []}}// student: &{2 student-2 2 {2 French []}}// student: &{3 student-3 1 {1 Math []}}// student: &{4 student-4 2 {2 French []}}// maps = [map[class:1 id:1 name:student-1] map[class:2 id:2 name:student-2] map[class:1 id:3 name:student-3] map[class:2 id:4 name:student-4]]// names = [student-1 student-2 student-3 student-4]// ids = [1 2 3 4]// bools = [true]// times = [2023-04-30 19:19:32 +0200 CEST]
Available Tags (SQL)
SQL:
korm.AutoMigrate[Tcomparable](tableNamestring,dbName...string)errorerr:= korm.AutoMigrate[User]("users")err:= korm.AutoMigrate[Bookmark ]("bookmarks")typeUserstruct {Idint`korm:"pk"`// AUTO Increment ID primary keyUuidstring`korm:"size:40"`// VARCHAR(50)Emailstring`korm:"size:50;iunique"`// insensitive uniquePasswordstring`korm:"size:150"`// VARCHAR(150)IsAdminbool`korm:"default:false"`// DEFAULT 0Imagestring`korm:"size:100;default:''"`CreatedAt time.Time`korm:"now"`// auto nowIgnoredstring`korm:"-"`}typeBookmarkstruct {Iduint`korm:"pk"`UserIdint`korm:"fk:users.id:cascade:setnull"`// options cascade,donothing/noaction, setnull/null, setdefault/defaultIsDoneboolToCheckstring`korm:"size:50; notnull; check: len(to_check) > 2 AND len(to_check) < 10; check: is_done=true"`// column type will be VARCHAR(50)Contentstring`korm:"text"`// column type will be TEXT not VARCHARUpdatedAt time.Time`korm:"update"`// will update when model updated, handled by triggers for sqlite, cockroach and postgres, and on migration for mysqlCreatedAt time.Time`korm:"now"`// now is default to current timestamp and of type TEXT for sqlite}all,_:=korm.Model[User]() .Where("id = ?",id) .Select("item1","item2") .OrderBy("created") .Limit(8) .Page(2) .All()
korm.New(dbTypeDialect,dbNamestring,dbDriverdriver.Driver,dbDSN...string)errorkorm.LogQueries()korm.GetConnection(dbName...string)*sql.DBkorm.To[Tany](dest*[]T,nestedSlice...bool)*Selector[T]// scan query to any type slice, even channels and slices with nested structs and joins(sl*Selector[T])Ctx(ctcontext.Context)*Selector[T](sl*Selector[T])Query(statementstring,args...any)error(sl*Selector[T])Named(statementstring,argsmap[string]any,unsafe...bool)errorkorm.WithBus(...opts)*ksbus.Server// Usage: WithBus(...opts) or share an existing onekorm.WithDashboard(address,...opts)*ksbus.Serverkorm.WithShell()korm.WithDocs(generateJsonDocsbool,outJsonDocsstring,handlerMiddlewares...func(handlerkmux.Handler)kmux.Handler)*ksbus.Serverkorm.WithEmbededDocs(embededembed.FS,embededDirPathstring,handlerMiddlewares...func(handler kmux.Handler) kmux.Handler)*ksbus.Serverkorm.WithMetrics(httpHandlerhttp.Handler)*ksbus.Serverkorm.WithPprof(path...string)*ksbus.Serverkorm.Transaction(dbName...string) (*sql.Tx,error)korm.Exec(dbName,querystring,args...any)errorkorm.ExecContext(ctxcontext.Context,dbName,querystring,args...any)errorkorm.ExecNamed(querystring,argsmap[string]any,dbName...string)errorkorm.ExecContextNamed(ctxcontext.Context,querystring,argsmap[string]any,dbName...string)errorkorm.BeforeServersData(fnfunc(dataany,conn*ws.Conn))korm.BeforeDataWS(fnfunc(datamap[string]any,conn*ws.Conn,originalRequest*http.Request)bool)korm.GetAllTables(dbName...string) []stringkorm.GetAllColumnsTypes(tablestring,dbName...string)map[string]stringkorm.GetMemoryTable(tbNamestring,dbName...string) (TableEntity,error)korm.GetMemoryTables(dbName...string) ([]TableEntity,error)korm.GetMemoryDatabases() []DatabaseEntitykorm.GetMemoryDatabase(dbNamestring) (*DatabaseEntity,error)korm.Shutdown(databasesName...string)errorkorm.FlushCache()korm.DisableCache()korm.ManyToMany(table1,table2string,dbName...string)error// add table relation m2m
korm.Exec(dbName,querystring,args...any)errorkorm.Transaction(dbName...string) (*sql.Tx,error)// Model is a starter for BuiderfuncModel[Tcomparable](tableName...string)*BuilderS[T]// Database allow to choose database to execute query onfunc (b*BuilderS[T])Database(dbNamestring)*BuilderS[T]// Insert insert a row into a table and return inserted PKfunc (b*BuilderS[T])Insert(model*T) (int,error)// InsertR add row to a table using input struct, and return the inserted rowfunc (b*BuilderS[T])InsertR(model*T) (T,error)// BulkInsert insert many row at the same time in one queryfunc (b*BuilderS[T])BulkInsert(models...*T) ([]int,error)// AddRelated used for many to many, and after korm.ManyToMany, to add a class to a student or a student to a class, class or student should exist in the database before adding themfunc (b*BuilderS[T])AddRelated(relatedTablestring,whereRelatedTablestring,whereRelatedArgs...any) (int,error)// DeleteRelated delete a relations many to manyfunc (b*BuilderS[T])DeleteRelated(relatedTablestring,whereRelatedTablestring,whereRelatedArgs...any) (int,error)// GetRelated used for many to many to get related classes to a student or related students to a classfunc (b*BuilderS[T])GetRelated(relatedTablestring,destany)error// JoinRelated same as get, but it join datafunc (b*BuilderS[T])JoinRelated(relatedTablestring,destany)error// Set used to update, Set("email,is_admin","example@mail.com",true) or Set("email = ? AND is_admin = ?","example@mail.com",true)func (b*BuilderS[T])Set(querystring,args...any) (int,error)// Delete data from database, can be multiple, depending on the where, return affected rows(Not every database or database driver may support affected rows)func (b*BuilderS[T])Delete() (int,error)// Drop drop table from dbfunc (b*BuilderS[T])Drop() (int,error)// Select usage: Select("email","password")func (b*BuilderS[T])Select(columns...string)*BuilderS[T]// Where can be like : Where("id > ?",1) or Where("id",1) = Where("id = ?",1)func (b*BuilderS[T])Where(querystring,args...any)*BuilderS[T]// Limit set limitfunc (b*BuilderS[T])Limit(limitint)*BuilderS[T]// Context allow to query or execute using ctxfunc (b*BuilderS[T])Context(ctx context.Context)*BuilderS[T]// Page return paginated elements using Limit for specific pagefunc (b*BuilderS[T])Page(pageNumberint)*BuilderS[T]// OrderBy can be used like: OrderBy("-id","-email") OrderBy("id","-email") OrderBy("+id","email")func (b*BuilderS[T])OrderBy(fields...string)*BuilderS[T]// Debug print prepared statement and values for this operationfunc (b*BuilderS[T])Debug()*BuilderS[T]// All get all datafunc (b*BuilderS[T])All() ([]T,error)// One get single rowfunc (b*BuilderS[T])One() (T,error)Examples:korm.Model[models.User]().Select("email","uuid").OrderBy("-id").Limit(PAGINATION_PER).Page(1).All()// INSERTuuid,_:=korm.GenerateUUID()hashedPass,_:=argon.Hash(password)korm.Model[models.User]().Insert(&models.User{Uuid:uuid,Email:"test@example.com",Password:hashedPass,IsAdmin:false,Image:"",CreatedAt:time.Now(),})//if using more than one dbkorm.Database[models.User]("dbNameHere").Where("id = ? AND email = ?",1,"test@example.com").All()// wherekorm.Model[models.User]().Where("id = ? AND email = ?",1,"test@example.com").One()// deletekorm.Model[models.User]().Where("id = ? AND email = ?",1,"test@example.com").Delete()// drop tablekorm.Model[models.User]().Drop()// updatekorm.Model[models.User]().Where("id = ?",1).Set("email = ?","new@example.com")
// BuilderM is query builder map string anytypeBuilderMstruct// Table is a starter for BuiderMfuncTable(tableNamestring)*BuilderM// Database allow to choose database to execute query onfunc (b*BuilderM)Database(dbNamestring)*BuilderM// Select select table columns to returnfunc (b*BuilderM)Select(columns...string)*BuilderM// Where can be like: Where("id > ?",1) or Where("id",1) = Where("id = ?",1)func (b*BuilderM)Where(querystring,args...any)*BuilderM// Limit set limitfunc (b*BuilderM)Limit(limitint)*BuilderM// Page return paginated elements using Limit for specific pagefunc (b*BuilderM)Page(pageNumberint)*BuilderM// OrderBy can be used like: OrderBy("-id","-email") OrderBy("id","-email") OrderBy("+id","email")func (b*BuilderM)OrderBy(fields...string)*BuilderM// Context allow to query or execute using ctxfunc (b*BuilderM)Context(ctx context.Context)*BuilderM// Debug print prepared statement and values for this operationfunc (b*BuilderM)Debug()*BuilderM// All get all datafunc (b*BuilderM)All() ([]map[string]any,error)// One get single rowfunc (b*BuilderM)One() (map[string]any,error)// Insert add row to a table using input map, and return PK of the inserted rowfunc (b*BuilderM)Insert(rowDatamap[string]any) (int,error)// InsertR add row to a table using input map, and return the inserted rowfunc (b*BuilderM)InsertR(rowDatamap[string]any) (map[string]any,error)// BulkInsert insert many row at the same time in one queryfunc (b*BuilderM)BulkInsert(rowsData...map[string]any) ([]int,error)// Set used to update, Set("email,is_admin","example@mail.com",true) or Set("email = ? AND is_admin = ?","example@mail.com",true)func (b*BuilderM)Set(querystring,args...any) (int,error)// Delete data from database, can be multiple, depending on the where, return affected rows(Not every database or database driver may support affected rows)func (b*BuilderM)Delete() (int,error)// Drop drop table from dbfunc (b*BuilderM)Drop() (int,error)// AddRelated used for many to many, and after korm.ManyToMany, to add a class to a student or a student to a class, class or student should exist in the database before adding themfunc (b*BuilderM)AddRelated(relatedTablestring,whereRelatedTablestring,whereRelatedArgs...any) (int,error)// GetRelated used for many to many to get related classes to a student or related students to a classfunc (b*BuilderM)GetRelated(relatedTablestring,dest*[]map[string]any)error// JoinRelated same as get, but it join datafunc (b*BuilderM)JoinRelated(relatedTablestring,dest*[]map[string]any)error// DeleteRelated delete a relations many to manyfunc (b*BuilderM)DeleteRelated(relatedTablestring,whereRelatedTablestring,whereRelatedArgs...any) (int,error)Examples:sliceMapStringAny,err:=korm.Table("users").Select("email","uuid").OrderBy("-id").Limit(PAGINATION_PER).Page(1).All()// INSERTuuid,_:=korm.GenerateUUID()hashedPass,_:=argon.Hash("password")// github.com/kamalshkeir/argonkorm.Table("users").Insert(map[string]any{"uuid":uuid,"email":"test@example.com",...})//if using more than one dbkorm.Database("dbNameHere").Table("tableName").Where("id = ? AND email = ?",1,"test@example.com").All()// whereWhere("id = ? AND email = ?",1,"test@example.com")// this workWhere("id,email",1,"test@example.com")// and this workkorm.Table("tableName").Where("id = ? AND email = ?",1,"test@example.com").One()// deletekorm.Table("tableName").Where("id = ? AND email = ?",1,"test@example.com").Delete()// drop tablekorm.Table("tableName").Drop()// updatekorm.Table("tableName").Where("id = ?",1).Set("email = ?","new@example.com")korm.Table("tableName").Where("id",1).Set("email","new@example.com")
korm.PaginationPer=10korm.DocsUrl="docs"korm.EmbededDashboard=falsekorm.MediaDir="media"korm.AssetsDir="assets"korm.StaticDir=path.Join(AssetsDir,"/","static")korm.TemplatesDir=path.Join(AssetsDir,"/","templates")korm.RepoUser="kamalshkeir"korm.RepoName="korm-dash"korm.adminPathNameGroup="/admin"// korm.SetAdminPath("/another")// so you can create a custom dashboard, upload it to your repos and change like like above korm.RepoUser and korm.RepoName
Example With Dashboard (you don't need korm.WithBus with it, because WithDashboard already call it and return the server bus for you)
package mainimport ("github.com/kamalshkeir/lg""github.com/kamalshkeir/ksmux""github.com/kamalshkeir/korm""github.com/kamalshkeir/sqlitedriver")funcmain() {err:=korm.New(korm.SQLITE,"db",sqlitedriver.Use())lg.CheckError(err)serverBus:=korm.WithDashboard("localhost:9313")korm.WithShell()// you can overwrite Admin and Auth middleware used for dashboard (dash_middlewares.go)//korm.Auth = func(handler ksmux.Handler) ksmux.Handler {}//korm.Admin = func(handler ksmux.Handler) ksmux.Handler {}// and also all handlers (dash_views.go)//korm.LoginView = func(c *ksmux.Context) {//c.Html("admin/new_admin_login.html", nil)//}// add extra static directory if you want//serverBus.App.LocalStatics("assets/mystatic","myassets") // will be available at /myassets/*//serverBus.App.LocalTemplates("assets/templates") // will make them available to use with c.Html// serve HTML// serverBus.App.Get("/",func(c *ksmux.Context) {// c.Html("index.html", map[string]any{// "data": data,// })// })serverBus.Run()// OR run https if you have certificatesserverBus.RunTLS(certstring,certKeystring)// OR generate certificates let's encrypt for a domain name, check https://github.com/kamalshkeir/ksbus for more infosserverBus.RunAutoTLS(subDomains...string)}
Then create admin user to connect to the dashboard
go run main.go shellcreatesuperuser
Then you can visit/admin
funcmain() {err:=korm.New(korm.SQLITE,"db",sqlitedriver.Use())iflg.CheckError(err) {return}deferkorm.Shutdown()srv:=korm.WithDashboard("localhost:9313")korm.WithShell()lg.Printfs("mgrunning on http://localhost:9313\n")app:=srv.Appapp.Get("/",korm.Auth(func(c*ksmux.Context) {// work with korm.Admin also// c.IsAuthenticated also return boolifv,ok:=c.User();ok {c.Json(map[string]any{"msg":"Authenticated","v":v.(korm.User).Email,})}else {c.Json(map[string]any{"error":"not auth",})}}))srv.Run()}
{{define "admin_nav"}}<headerid="admin-header"><nav><ahref="/"><h1>KORM</h1></a><ul><li><a{{ifeq.Request.URL.Path "/" }}class="active"{{end}}href="/">Home</a></li><li><a{{ifcontains.Request.URL.Path.admin_path}}class="active"{{end}}href="{{.admin_path}}">Admin</a></li> {{if .IsAuthenticated}}<li><ahref="{{.admin_path}}/logout">Logout</a></li> {{if .User.Email}}<li><span>Hello {{.User.Email}}</span></li> {{end}} {{end}}</ul></nav></header>{{end}}// dash_middlewares.gopackage kormimport ("context""net/http""github.com/kamalshkeir/aes""github.com/kamalshkeir/ksmux")varAuth=func(handler ksmux.Handler) ksmux.Handler {returnfunc(c*ksmux.Context) {session,err:=c.GetCookie("session")iferr!=nil||session=="" {// NOT AUTHENTICATEDc.DeleteCookie("session")handler(c)return}session,err=aes.Decrypt(session)iferr!=nil {handler(c)return}// Check sessionuser,err:=Model[User]().Where("uuid = ?",session).One()iferr!=nil {// session failhandler(c)return}// AUTHENTICATED AND FOUND IN DBc.SetKey("korm-user",user)handler(c)}}varAdmin=func(handler ksmux.Handler) ksmux.Handler {returnfunc(c*ksmux.Context) {session,err:=c.GetCookie("session")iferr!=nil||session=="" {// NOT AUTHENTICATEDc.DeleteCookie("session")c.Status(http.StatusTemporaryRedirect).Redirect(adminPathNameGroup+"/login")return}session,err=aes.Decrypt(session)iferr!=nil {c.Status(http.StatusTemporaryRedirect).Redirect(adminPathNameGroup+"/login")return}user,err:=Model[User]().Where("uuid = ?",session).One()iferr!=nil {// AUTHENTICATED BUT NOT FOUND IN DBc.Status(http.StatusTemporaryRedirect).Redirect(adminPathNameGroup+"/login")return}// Not adminif!user.IsAdmin {c.Status(403).Text("Middleware : Not allowed to access this page")return}c.SetKey("korm-user",user)handler(c)}}varBasicAuth=func(handler ksmux.Handler) ksmux.Handler {returnksmux.BasicAuth(handler,BASIC_AUTH_USER,BASIC_AUTH_PASS)}
KORM 1:
package mainimport ("net/http""github.com/kamalshkeir/lg""github.com/kamalshkeir/ksmux""github.com/kamalshkeir/ksmux/ws""github.com/kamalshkeir/korm""github.com/kamalshkeir/ksbus""github.com/kamalshkeir/sqlitedriver")funcmain() {err:=korm.New(korm.SQLITE,"db1",sqlitedriver.Use())iflg.CheckError(err) {return}korm.WithShell()serverBus:=korm.WithBus(ksbus.ServerOpts{IDstringAddressstringPathstringOnWsClosefunc(connIDstring)OnDataWSfunc(datamap[string]any,conn*ws.Conn,originalRequest*http.Request)errorOnServerDatafunc(dataany,conn*ws.Conn)OnIdfunc(datamap[string]any)OnUpgradeWsfunc(r*http.Request)boolWithOtherRouter*ksmux.RouterWithOtherBus*Bus})// handler authenticationkorm.BeforeDataWS(func(datamap[string]any,conn*ws.Conn,originalRequest*http.Request)bool {lg.Info("handle authentication here")returntrue})// handler data from other KORMkorm.BeforeServersData(func(dataany,conn*ws.Conn) {lg.Info("recv orm:","data",data)})// built in router to the bus, check it at https://github.com/kamalshkeir/ksbusserverBus.App.Get("/",func(c*ksmux.Context) {serverBus.SendToServer("localhost:9314",map[string]any{"msg":"hello from server 1",})c.Text("ok")})serverBus.Run("localhost:9313")// OR run https if you have certificatesserverBus.RunTLS(addrstring,certstring,certKeystring)// OR generate certificates let's encrypt for a domain name, check https://github.com/kamalshkeir/ksbus for more detailsserverBus.RunAutoTLS(domainNamestring,subDomains...string)}
KORM 2:
package mainimport ("net/http""github.com/kamalshkeir/lg""github.com/kamalshkeir/ksmux""github.com/kamalshkeir/ksmux/ws""github.com/kamalshkeir/korm""github.com/kamalshkeir/sqlitedriver")funcmain() {err:=korm.New(korm.SQLITE,"db2",sqlitedriver.Use())iflg.CheckError(err) {return}korm.WithShell()// if dashboard used, this line should be after itserverBus:=korm.WithBus(ksbus.ServerOpts{IDstringAddressstringPathstringOnWsClosefunc(connIDstring)OnDataWSfunc(datamap[string]any,conn*ws.Conn,originalRequest*http.Request)errorOnServerDatafunc(dataany,conn*ws.Conn)OnIdfunc(datamap[string]any)OnUpgradeWsfunc(r*http.Request)boolWithOtherRouter*ksmux.RouterWithOtherBus*Bus})korm.BeforeServersData(func(dataany,conn*ws.Conn) {lg.Info("recv","data",data)})// built in router to the bus, check it at https://github.com/kamalshkeir/ksbusserverBus.App.GET("/",func(c*ksmux.Context) {serverBus.SendToServer("localhost:9314",map[string]any{"msg":"hello from server 2",})c.Status(200).Text("ok")})// Run Server BusserverBus.Run("localhost:9314")// OR run https if you have certificatesserverBus.RunTLS(addrstring,certstring,certKeystring)// OR generate certificates let's encrypt for a domain name, check https://github.com/kamalshkeir/ksbus for more infosserverBus.RunAutoTLS(domainNamestring,subDomains...string)}
// generated example using concatination and lengthtypeTestUserstruct {Id*uint`korm:"pk"`Uuidstring`korm:"size:40;iunique"`Email*string`korm:"size:100;iunique"`Genstring`korm:"size:250;generated: concat(uuid,'working',len(password))"`PasswordstringIsAdmin*boolCreatedAt time.Time`korm:"now"`UpdatedAt time.Time`korm:"update"`}funcTestGeneratedAs(t*testing.T) {u,err:=Model[TestUser]().Limit(3).All()iferr!=nil {t.Error(err)}iflen(u)!=3 {t.Error("len not 20")}ifu[0].Gen!=u[0].Uuid+"working"+fmt.Sprintf("%d",len(u[0].Password)) {t.Error("generated not working:",u[0].Gen)}}
// Where examplefuncTestConcatANDLen(t*testing.T) {groupes,err:=Model[Group]().Where("name = concat(?,'min') AND len(name) = ?","ad",5).Debug().All()// translated to select * from groups WHERE name = 'ad' || 'min' AND length(name) = 5 (sqlite)// translated to select * from groups WHERE name = concat('ad','min') AND char_length(name) = 5 (postgres, mysql)iferr!=nil {t.Error(err)}iflen(groupes)!=1||groupes[0].Name!="admin" {t.Error("len(groupes) != 1 , got: ",groupes)}}
Learn more aboutKsmux
funcmain() {err:=korm.New(korm.SQLITE,"db",sqlitedriver.Use())iferr!=nil {log.Fatal(err)}serverBus:=korm.WithDashboard("localhost:9313")korm.WithShell()mux:=serverBus.App// add global middlewaresmux.Use((midws...func(http.Handler) http.Handler))...}
serverBus:=korm.WithDashboard("localhost:9313")// or srv := korm.WithBus()serverBus.WithPprof(path...string)// path is 'debug' by defaultwillenable:-/debug/pprof-/debug/profile-/debug/heap-/debug/trace
To execute profile cpu:go tool pprof -http=":8000" pprofbin http://localhost:9313/debug/profile?seconds=18To execute profile memory:go tool pprof -http=":8000" pprofbin http://localhost:9313/debug/heap?seconds=18To execute generate trace: go to endpointhttp://localhost:9313/debug/trace?seconds=18 from browser , this will download the trace of 18 secondsThen to see the trace :go tool trace path/to/trace
// or srv := korm.WithBus()//srv.WithMetrics(httpHandler http.Handler, path ...string) path default to 'metrics'srv.WithMetrics(promhttp.Handler())willenable:-/metrics
// or srv := korm.WithBus()//srv.WithMetrics(httpHandler http.Handler, path ...string) path default to 'metrics'srv.App.Use(ksmux.Logs())// it take an optional callback executed on each request if you want to add log to a file or sendsrv.App.Use(ksmux.Logs(func(method,path,remotestring,statusint,took time.Duration) {// save somewhere}))willenable:-/metrics
korm.OnInsert(func(database,tablestring,datamap[string]any)error {fmt.Println("inserting into",database,table,data)// if error returned, it will not insertreturnnil})korm.OnSet(func(database,tablestring,datamap[string]any)error {fmt.Println("set into",database,table,data)returnnil})korm.OnDelete(func(database,table,querystring,args...any)error {})korm.OnDrop(func(database,tablestring)error {})
pip install ksbus==1.1.0# if it doesn't work , execute it againfromksbusimportBus# onOpen callback that let you know when connection is ready, it take the bus as paramdefOnOpen(bus):print("connected")# bus.autorestart=True# Publish publish to topicbus.Publish("top", {"data":"hello from python" })# Subscribe, it also return the subscriptionbus.Subscribe("python",pythonTopicHandler)# SendToNamed publish to named topicbus.SendToNamed("top:srv", {"data":"hello again from python" })# bus.Unsubscribe("python")print("finish everything")# pythonTopicHandler handle topic 'python'defpythonTopicHandler(data,subs):print("recv on topic python:",data)# Unsubscribe#subs.Unsubscribe()if__name__=="__main__":Bus("localhost:9313",onOpen=OnOpen)# blockingprint("prorgram exited")
typeClassstruct {Iduint`korm:"pk"`Namestring`korm:"size:100"`IsAvailableboolCreatedAt time.Time`korm:"now"`}typeStudentstruct {Iduint`korm:"pk"`Namestring`korm:"size:100"`CreatedAt time.Time`korm:"now"`}// migratefuncmigrate() {err:= korm.AutoMigrate[Class]("classes")iflg.CheckError(err) {return}err= korm.AutoMigrate[Student]("students")iflg.CheckError(err) {return}err=korm.ManyToMany("classes","students")iflg.CheckError(err) {return}}// korm.ManyToMany create relation table named m2m_classes_students// then you can use it like so to get related data// get related to map to structstd:= []Student{}err=korm.Model[Class]().Where("name = ?","Math").Select("name").OrderBy("-name").Limit(1).GetRelated("students",&std)// get related to mapstd:= []map[string]any{}err=korm.Table("classes").Where("name = ?","Math").Select("name").OrderBy("-name").Limit(1).GetRelated("students",&std)// join related to mapstd:= []map[string]any{}err=korm.Table("classes").Where("name = ?","Math").JoinRelated("students",&std)// join related to strcucu:= []JoinClassUser{}err=korm.Model[Class]().Where("name = ?","Math").JoinRelated("students",&cu)// to add relation_,err=korm.Model[Class]().AddRelated("students","name = ?","hisName")_,err=korm.Model[Student]().AddRelated("classes","name = ?","French")_,err=korm.Table("students").AddRelated("classes","name = ?","French")// delete relation_,err=korm.Model[Class]().Where("name = ?","Math").DeleteRelated("students","name = ?","hisName")_,err=korm.Table("classes").Where("name = ?","Math").DeleteRelated("students","name = ?","hisName")
korm.DocsUrl="docs"// default endpoint '/docs'korm.BASIC_AUTH_USER="test"korm.BASIC_AUTH_PASS="pass"korm.WithDocs(generate,dirPath,korm.BasicAuth)korm.WithDocs(true,"",korm.BasicAuth)// dirPath default to 'assets/static/docs'korm.WithEmbededDocs(embededembed.FS,dirPath,korm.BasicAuth)// dirPath default to 'assets/static/docs' if empty
Commands: [databases, use, tables, columns, migrate, createsuperuser, createuser, query, getall, get, drop, delete, clear/cls, q/quit/exit, help/commands]'databases': list all connected databases'use': use a specific database'tables': list all tablesin database'columns': list all columns of a table (accept but not required extra param like:'columns' or'columns users')'migrate': migrate or execute sql file'createsuperuser': (only with dashboard) create a admin user'createuser': (only with dashboard) create a regular user'query': query data from database (accept but not required extra param like:'query' or'query select * from users where ...')'getall': get all rows given a table name (accept but not required extra param like:'getall' or'getall users')'get': get single row (accept but not required extra param like:'get' or'get users email like "%anything%"')'delete': delete rows where field equal_to (accept but not required extra param like:'delete' or'delete users email="email@example.com"')'drop': drop a table given table name (accept but not required extra param like:'drop' or'drop users')'clear / cls': clear shell console'q / quit / exit / q!':exit shell'help': show thishelp message
import"github.com/kamalshkeir/kenv"typeEmbedSstruct {Staticbool`kenv:"EMBED_STATIC|false"`Templatesbool`kenv:"EMBED_TEMPLATES|false"`}typeGlobalConfigstruct {Hoststring`kenv:"HOST|localhost"`// DEFAULT to 'localhost': if HOST not found in envPortstring`kenv:"PORT|9313"`EmbedEmbedSDbstruct {Namestring`kenv:"DB_NAME|db"`// NOT REQUIRED: if DB_NAME not found, defaulted to 'db'Typestring`kenv:"DB_TYPE"`// REEQUIRED: this env var is required, you will have error if emptyDSNstring`kenv:"DB_DSN|"`// NOT REQUIRED: if DB_DSN not found it's not required, it's ok to stay empty}Smtpstruct {Emailstring`kenv:"SMTP_EMAIL|"`Passstring`kenv:"SMTP_PASS|"`Hoststring`kenv:"SMTP_HOST|"`Portstring`kenv:"SMTP_PORT|"`}Profilerbool`kenv:"PROFILER|false"`Docsbool`kenv:"DOCS|false"`Logsbool`kenv:"LOGS|false"`Monitoringbool`kenv:"MONITORING|false"`}kenv.Load(".env")// load env file// Fill struct from env loaded before:Config:=&GlobalConfig{}err:=kenv.Fill(Config)// fill struct with env vars loaded before
package mainimport ("fmt""time""github.com/kamalshkeir/lg""github.com/kamalshkeir/korm""github.com/kamalshkeir/sqlitedriver")typeClassstruct {Iduint`korm:"pk"`NamestringStudents []Student}typeStudentstruct {Iduint`korm:"pk"`NamestringClassuint`korm:"fk:classes.id:cascade:cascade"`ClassesClass}funcmain() {err:=korm.New(korm.SQLITE,"db",sqlitedriver.Use())iflg.CheckError(err) {return}deferkorm.Shutdown()server:=korm.WithDashboard("localhost:9313")korm.WithShell()err= korm.AutoMigrate[Class]("classes")lg.CheckError(err)err= korm.AutoMigrate[Student]("students")lg.CheckError(err)// go run main.go shell to createsuperuser// connect to admin and create some data to query// nested structs with joins, scan the result to the channel directly after each row// so instead of receiving a slice, you will receive data on the channel[0] of the passed slicestudentsChan:= []chanStudent{make(chanStudent)}gofunc() {fors:=rangestudentsChan[0] {fmt.Println("chan students:",s)}}()err=korm.To(&studentsChan).Query("select students.*,classes.id as 'classes.id',classes.name as 'classes.name' from students join classes where classes.id = students.class")lg.CheckError(err)fmt.Println()// nested (second argument of 'Scan') filled automatically from join, support nested slices and structsclasses:= []Class{}err=korm.To(&classes,true).Query("select classes.*, students.id as 'students.id',students.name as 'students.name' from classes join students on students.class = classes.id order by classes.id")lg.CheckError(err)for_,s:=rangeclasses {fmt.Println("class:",s)}fmt.Println()// // not nested, only remove second arg true from Scan methodstudents:= []Student{}err=korm.To(&students,true).Query("select students.*,classes.id as 'classes.id',classes.name as 'classes.name' from students join classes where classes.id = students.class")lg.CheckError(err)for_,s:=rangestudents {fmt.Println("student:",s)}fmt.Println()maps:= []map[string]any{}err=korm.To(&maps).Query("select * from students")lg.CheckError(err)fmt.Println("maps =",maps)fmt.Println()names:= []*string{}err=korm.To(&names).Query("select name from students")lg.CheckError(err)fmt.Println("names =",names)fmt.Println()ids:= []int{}err=korm.To(&ids).Query("select id from students")lg.CheckError(err)fmt.Println("ids =",ids)fmt.Println()bools:= []bool{}err=korm.To(&bools).Query("select is_admin from users")lg.CheckError(err)fmt.Println("bools =",bools)fmt.Println()times:= []time.Time{}err=korm.To(×).Query("select created_at from users")lg.CheckError(err)fmt.Println("times =",times)server.Run()}// OUTPUT// chan students: {1 student-1 1 {1 Math []}}// chan students: {2 student-2 2 {2 French []}}// chan students: {3 student-3 1 {1 Math []}}// chan students: {4 student-4 2 {2 French []}}// class: {1 Math [{1 student-1 0 {0 []}} {3 student-3 0 {0 []}}]}// class: {2 French [{2 student-2 0 {0 []}} {4 student-4 0 {0 []}}]}// student: &{1 student-1 1 {1 Math []}}// student: &{2 student-2 2 {2 French []}}// student: &{3 student-3 1 {1 Math []}}// student: &{4 student-4 2 {2 French []}}// maps = [map[class:1 id:1 name:student-1] map[class:2 id:2 name:student-2] map[class:1 id:3 name:student-3] map[class:2 id:4 name:student-4]]// names = [student-1 student-2 student-3 student-4]// ids = [1 2 3 4]// bools = [true]// times = [2023-04-30 19:19:32 +0200 CEST]
https://github.com/kamalshkeir/korm-vs-gorm-vs-tarantool-vs-pgx
goos: windowsgoarch: amd64pkg: github.com/kamalshkeir/korm/benchmarkscpu: Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz
To execute these benchmarks on your machine, very easy :
- git clonehttps://github.com/kamalshkeir/korm.git
- cd korm
- uncomment commented code at benchmarks/bench-test.go and Save
- go mod tidy
- go test -bench ^ .\benchmarks\ -benchmem
typeTestTablestruct {Iduint`korm:"pk"`EmailstringContentstringPasswordstringIsAdminboolCreatedAt time.Time`korm:"now"`UpdatedAt time.Time`korm:"update"`}typeTestTableGormstruct {Iduint`gorm:"primarykey"`EmailstringContentstringPasswordstringIsAdminboolCreatedAt time.TimeUpdatedAt time.Time}//////////////////////////////////////////// query 7000 rows //////////////////////////////////////////////BenchmarkGetAllS_GORM-41956049832ns/op12163316B/op328790allocs/opBenchmarkGetAllS-42708934395.3ns/op224B/op1allocs/opBenchmarkGetAllM_GORM-41862989567ns/op13212278B/op468632allocs/opBenchmarkGetAllM-44219461273.5ns/op224B/op1allocs/opBenchmarkGetRowS_GORM-41218896988ns/op5930B/op142allocs/opBenchmarkGetRowS-41473164805.1ns/op336B/op7allocs/opBenchmarkGetRowM_GORM-411402101638ns/op7408B/op203allocs/opBenchmarkGetRowM-41752652671.9ns/op336B/op7allocs/opBenchmarkPagination10_GORM-47714153304ns/op19357B/op549allocs/opBenchmarkPagination10-41285722934.5ns/op400B/op7allocs/opBenchmarkPagination100_GORM-41364738934ns/op165423B/op4704allocs/opBenchmarkPagination100-41278724956.5ns/op400B/op7allocs/opBenchmarkQueryS-45781499207.7ns/op4B/op1allocs/opBenchmarkQueryM-44643155227.2ns/op4B/op1allocs/opBenchmarkGetAllTables-44746586525.48ns/op0B/op0allocs/opBenchmarkGetAllColumns-42365701942.82ns/op0B/op0allocs/op//////////////////////////////////////////// query 5000 rows //////////////////////////////////////////////BenchmarkGetAllS_GORM-42443247546ns/op8796840B/op234784allocs/opBenchmarkGetAllS-42854401426.8ns/op224B/op1allocs/opBenchmarkGetAllM_GORM-42446329242ns/op9433050B/op334631allocs/opBenchmarkGetAllM-44076317283.4ns/op224B/op1allocs/opBenchmarkGetRowS_GORM-411445101107ns/op5962B/op142allocs/opBenchmarkGetRowS-41344831848.4ns/op336B/op7allocs/opBenchmarkGetRowM_GORM-410000100969ns/op7440B/op203allocs/opBenchmarkGetRowM-41721742688.5ns/op336B/op7allocs/opBenchmarkPagination10_GORM-47500156208ns/op19423B/op549allocs/opBenchmarkPagination10-41253757952.3ns/op400B/op7allocs/opBenchmarkPagination100_GORM-41564749408ns/op165766B/op4704allocs/opBenchmarkPagination100-41236270957.5ns/op400B/op7allocs/opBenchmarkGetAllTables-44439938625.43ns/op0B/op0allocs/opBenchmarkGetAllColumns-42790639241.45ns/op0B/op0allocs/op//////////////////////////////////////////// query 1000 rows //////////////////////////////////////////////BenchmarkGetAllS_GORM-41636766871ns/op1683919B/op46735allocs/opBenchmarkGetAllS-42882660399.0ns/op224B/op1allocs/opBenchmarkGetAllM_GORM-41408344988ns/op1886922B/op66626allocs/opBenchmarkGetAllM-43826730296.5ns/op224B/op1allocs/opBenchmarkGetRowS_GORM-41194097725ns/op5935B/op142allocs/opBenchmarkGetRowS-41333258903.0ns/op336B/op7allocs/opBenchmarkGetRowM_GORM-410000106079ns/op7408B/op203allocs/opBenchmarkGetRowM-41601274748.2ns/op336B/op7allocs/opBenchmarkPagination10_GORM-47534159991ns/op19409B/op549allocs/opBenchmarkPagination10-411539821022ns/op400B/op7allocs/opBenchmarkPagination100_GORM-41468766269ns/op165876B/op4705allocs/opBenchmarkPagination100-410000001016ns/op400B/op7allocs/opBenchmarkGetAllTables-45620029725.36ns/op0B/op0allocs/opBenchmarkGetAllColumns-42547867941.30ns/op0B/op0allocs/op//////////////////////////////////////////// query 300 rows //////////////////////////////////////////////BenchmarkGetAllS_GORM-45582046830ns/op458475B/op13823allocs/opBenchmarkGetAllS-42798872411.5ns/op224B/op1allocs/opBenchmarkGetAllM_GORM-44282605646ns/op567011B/op19721allocs/opBenchmarkGetAllM-44093662287.9ns/op224B/op1allocs/opBenchmarkGetRowS_GORM-41218297764ns/op5966B/op142allocs/opBenchmarkGetRowS-41347084886.4ns/op336B/op7allocs/opBenchmarkGetRowM_GORM-410000105311ns/op7440B/op203allocs/opBenchmarkGetRowM-41390363780.0ns/op336B/op7allocs/opBenchmarkPagination10_GORM-47502155949ns/op19437B/op549allocs/opBenchmarkPagination10-410000001046ns/op400B/op7allocs/opBenchmarkPagination100_GORM-41479779700ns/op165679B/op4705allocs/opBenchmarkPagination100-410000001054ns/op400B/op7allocs/opBenchmarkGetAllTables-45225570426.00ns/op0B/op0allocs/opBenchmarkGetAllColumns-42929236842.09ns/op0B/op0allocs/op//////////////////////////////////////////// MONGO //////////////////////////////////////////////BenchmarkGetAllS-43121384385.6ns/op224B/op1allocs/opBenchmarkGetAllM-44570059264.2ns/op224B/op1allocs/opBenchmarkGetRowS-41404399866.6ns/op336B/op7allocs/opBenchmarkGetRowM-41691026722.6ns/op336B/op7allocs/opBenchmarkGetAllTables-44742448925.34ns/op0B/op0allocs/opBenchmarkGetAllColumns-42703963242.22ns/op0B/op0allocs/op//////////////////////////////////////////////////////////////////////////////////////////////////////////
| Without parameter | With parameter |
|---|---|
| |
| Without parameter |
|---|
|
| With parameter |
|
Bool : bool is INTEGER NOT NULL checked between 0 and 1 (in order to be consistent accross sql dialects)
| Without parameter | With parameter |
|---|---|
| |
| Without parameter | With parameter |
|---|---|
| |
| Without parameter | With parameter |
|---|---|
| |
typeJsonOptionstruct {AsstringDialectstringDatabasestringParams []any}funcJSON_EXTRACT(dataJsonstring,opt...JsonOption)stringfuncJSON_REMOVE(dataJsonstring,opt...JsonOption)stringfuncJSON_SET(dataJsonstring,opt...JsonOption)stringfuncJSON_ARRAY(values []any,asstring,dialect...string)stringfuncJSON_OBJECT(values []any,asstring,dialect...string)stringfuncJSON_CAST(valuestring,asstring,dialect...string)string// create query jsonq:=korm.JSON_EXTRACT(`{"a": {"c": 3}, "b": 2}`, korm.JsonOption{As:"data",Params: []any{"a.c","b"},})fmt.Println("q ==",q)// q == JSON_EXTRACT('{"a": {"c": 3}, "b": 2}','$.a.c','$.b') AS datavardata []map[string]anyerr:=korm.To(&data).Query("SELECT "+q)lg.CheckError(err)fmt.Println("data=",data)// data= [map[data:[3,2]]]
LicenceBSD-3
About
KORM, an elegant and lightning-fast ORM for all your concurrent and async needs. Inspired by the highly popular Django Framework, KORM offers similar functionality with the added bonus of performance
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors2
Uh oh!
There was an error while loading.Please reload this page.


