korm

package module
v1.96.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 30, 2025 License: BSD-3-Clause Imports: 38 Imported by: 2

README ¶




It is also composable, allowing for integration with a network websocket PubSub using WithBus when you want to synchronise your data between multiple Korm or WithDashboard to have a complete setup of server bus and Admin Dashboard.

Why settle for less when you can have the best ?

  • 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

It Has :

  • 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 it

  • New: 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

  • Auto Docs with Model API and video tutoriel

  • Swagger Documentation and tutorial

  • PPROF Go profiling tool and Metrics Prometheus

  • Logs Middleware

  • Admin dashboard with ready offline and installable PWA (using /static/sw.js and /static/manifest.webmanifest). All statics mentionned in sw.js will be cached and served by the service worker, you can inspect the Network Tab in the browser to check it

  • Shared 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, see Example

  • Built-in Authentication using korm.Auth , korm.Admin or korm.BasicAuth middlewares, whenever Auth and Admin middlewares are used, you get access to the .User model and variable .IsAuthenticated from any template html like this example admin_nav.html

  • Interactive 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) or korm.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 Where and for tags: check and generated (all dialects)

  • Support for foreign keys, indexes , checks,... See all

  • Kenv load env vars to struct

  • Python Bus Client pip install ksbus

All drivers concurrent safe read and write

Supported databases:

  • Sqlite
  • Mysql
  • Maria
  • Postgres
  • Cockroach

Installation

go get -u github.com/kamalshkeir/korm@latest

Drivers moved outside this package to not get them all in your go.mod file

go get -u github.com/kamalshkeir/sqlitedriver@latest
go get -u github.com/kamalshkeir/pgdriver@latest
go get -u github.com/kamalshkeir/mysqldriver@latest

Global Vars

// Debug when true show extra useful logs for queries executed for migrations and queries statements
Debug = false
// FlushCacheEvery execute korm.FlushCache() every 10 min by default, you should not worry about it, but useful that you can change it
FlushCacheEvery = 10 * time.Minute
// SetCacheMaxMemory set max size of each cache cacheAllS AllM ...
korm.SetCacheMaxMemory(megaByte int) // default maximum of 50 Mb , cannot be lower
// Connection pool
MaxOpenConns = 20
MaxIdleConns = 20
MaxLifetime = 30 * time.Minute
MaxIdleTime = 15 * time.Minute

Connect to a database

// sqlite
// go get github.com/kamalshkeir/sqlitedriver
err := korm.New(korm.SQLITE, "dbName", sqlitedriver.Use()) // Connect
// postgres, cockroach
// go get github.com/kamalshkeir/pgdriver
err := korm.New(korm.POSTGRES,"dbName", pgdriver.Use(), "user:password@localhost:5432") // Connect
// mysql, maria
// go get github.com/kamalshkeir/mysqldriver
err := korm.New(korm.MYSQL,"dbName", mysqldriver.Use(), "user:password@localhost:3306") // Connect

korm.Shutdown(databasesName ...string) error

Hello world example

package main

import (
	"fmt"
	"time"

	"github.com/kamalshkeir/lg"
	"github.com/kamalshkeir/korm"
	"github.com/kamalshkeir/sqlitedriver"
)

type Class struct {
	Id       uint `korm:"pk"`
	Name     string
	Students []Student
}

type Student struct {
	Id      uint `korm:"pk"`
	Name    string
	Class   uint `korm:"fk:classes.id:cascade:cascade"`
	Classes Class
}

func main() {
	err := korm.New(korm.SQLITE, "db", sqlitedriver.Use())
	if lg.CheckError(err) {
		return
	}
	defer korm.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 slice
	studentsChan := []chan Student{make(chan Student)}
	go func() {
		for s := range studentsChan[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 structs
	classes := []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 := range classes {
		fmt.Println("class:", s)
	}
	fmt.Println()

	// // not nested, only remove second arg true from 'To' method
	students := []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 := range students {
		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(&times).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]

AutoMigrate

Available Tags (SQL)

SQL:

korm.AutoMigrate[T comparable](tableName string, dbName ...string) error 

err := korm.AutoMigrate[User]("users")
err := korm.AutoMigrate[Bookmark ]("bookmarks")

type User struct {
	Id        int       `korm:"pk"` // AUTO Increment ID primary key
	Uuid      string    `korm:"size:40"` // VARCHAR(50)
	Email     string    `korm:"size:50;iunique"` // insensitive unique
	Password  string    `korm:"size:150"` // VARCHAR(150)
	IsAdmin   bool      `korm:"default:false"` // DEFAULT 0
	Image     string    `korm:"size:100;default:''"`
	CreatedAt time.Time `korm:"now"` // auto now
    Ignored   string    `korm:"-"`
}

type Bookmark struct {
	Id      uint   `korm:"pk"`
	UserId  int    `korm:"fk:users.id:cascade:setnull"` // options cascade,donothing/noaction, setnull/null, setdefault/default
	IsDone	bool   
	ToCheck string `korm:"size:50; notnull; check: len(to_check) > 2 AND len(to_check) < 10; check: is_done=true"`  // column type will be VARCHAR(50)
	Content string `korm:"text"` // column type will be TEXT not VARCHAR
	UpdatedAt time.Time `korm:"update"` // will update when model updated, handled by triggers for sqlite, cockroach and postgres, and on migration for mysql
	CreatedAt 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()

API

korm.New(dbType Dialect, dbName string, dbDriver driver.Driver, dbDSN ...string) error
korm.LogQueries()
korm.GetConnection(dbName ...string) *sql.DB
korm.To[T any](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(ct context.Context) *Selector[T]
(sl *Selector[T]) Query(statement string, args ...any) error
(sl *Selector[T]) Named(statement string, args map[string]any, unsafe ...bool) error
korm.WithBus(...opts) *ksbus.Server // Usage: WithBus(...opts) or share an existing one
korm.WithDashboard(address, ...opts) *ksbus.Server
korm.WithShell()
korm.WithDocs(generateJsonDocs bool, outJsonDocs string, handlerMiddlewares ...func(handler kmux.Handler) kmux.Handler) *ksbus.Server
korm.WithEmbededDocs(embeded embed.FS, embededDirPath string, handlerMiddlewares ...func(handler kmux.Handler) kmux.Handler) *ksbus.Server
korm.WithMetrics(httpHandler http.Handler) *ksbus.Server
korm.WithPprof(path ...string) *ksbus.Server
korm.Transaction(dbName ...string) (*sql.Tx, error)
korm.Exec(dbName, query string, args ...any) error
korm.ExecContext(ctx context.Context, dbName, query string, args ...any) error
korm.ExecNamed(query string, args map[string]any, dbName ...string) error
korm.ExecContextNamed(ctx context.Context, query string, args map[string]any, dbName ...string) error
korm.BeforeServersData(fn func(data any, conn *ws.Conn))
korm.BeforeDataWS(fn func(data map[string]any, conn *ws.Conn, originalRequest *http.Request) bool)
korm.GetAllTables(dbName ...string) []string
korm.GetAllColumnsTypes(table string, dbName ...string) map[string]string
korm.GetMemoryTable(tbName string, dbName ...string) (TableEntity, error)
korm.GetMemoryTables(dbName ...string) ([]TableEntity, error)
korm.GetMemoryDatabases() []DatabaseEntity
korm.GetMemoryDatabase(dbName string) (*DatabaseEntity, error)
korm.Shutdown(databasesName ...string) error
korm.FlushCache()
korm.DisableCache() 
korm.ManyToMany(table1, table2 string, dbName ...string) error // add table relation m2m 

Builder Struct:

korm.Exec(dbName, query string, args ...any) error
korm.Transaction(dbName ...string) (*sql.Tx, error)
// Model is a starter for Buider
func Model[T comparable](tableName ...string) *BuilderS[T]
// Database allow to choose database to execute query on
func (b *BuilderS[T]) Database(dbName string) *BuilderS[T]
// Insert insert a row into a table and return inserted PK
func (b *BuilderS[T]) Insert(model *T) (int, error)
// InsertR add row to a table using input struct, and return the inserted row
func (b *BuilderS[T]) InsertR(model *T) (T, error)
// BulkInsert insert many row at the same time in one query
func (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 them
func (b *BuilderS[T]) AddRelated(relatedTable string, whereRelatedTable string, whereRelatedArgs ...any) (int, error)
// DeleteRelated delete a relations many to many
func (b *BuilderS[T]) DeleteRelated(relatedTable string, whereRelatedTable string, whereRelatedArgs ...any) (int, error)
// GetRelated used for many to many to get related classes to a student or related students to a class
func (b *BuilderS[T]) GetRelated(relatedTable string, dest any) error
// JoinRelated same as get, but it join data
func (b *BuilderS[T]) JoinRelated(relatedTable string, dest any) 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(query string, 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 db
func (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(query string, args ...any) *BuilderS[T]
// Limit set limit
func (b *BuilderS[T]) Limit(limit int) *BuilderS[T]
// Context allow to query or execute using ctx
func (b *BuilderS[T]) Context(ctx context.Context) *BuilderS[T]
// Page return paginated elements using Limit for specific page
func (b *BuilderS[T]) Page(pageNumber int) *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 operation
func (b *BuilderS[T]) Debug() *BuilderS[T]
// All get all data
func (b *BuilderS[T]) All() ([]T, error)
// One get single row
func (b *BuilderS[T]) One() (T, error)

Examples:
korm.Model[models.User]().Select("email","uuid").OrderBy("-id").Limit(PAGINATION_PER).Page(1).All()

// INSERT
uuid,_ := 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 db
korm.Database[models.User]("dbNameHere").Where("id = ? AND email = ?",1,"test@example.com").All() 

// where
korm.Model[models.User]().Where("id = ? AND email = ?",1,"test@example.com").One() 

// delete
korm.Model[models.User]().Where("id = ? AND email = ?",1,"test@example.com").Delete()

// drop table
korm.Model[models.User]().Drop()

// update
korm.Model[models.User]().Where("id = ?",1).Set("email = ?","new@example.com")

Builder map[string]any:

// BuilderM is query builder map string any
type BuilderM struct
// Table is a starter for BuiderM
func Table(tableName string) *BuilderM
// Database allow to choose database to execute query on
func (b *BuilderM) Database(dbName string) *BuilderM
// Select select table columns to return
func (b *BuilderM) Select(columns ...string) *BuilderM
// Where can be like: Where("id > ?",1) or Where("id",1) = Where("id = ?",1)
func (b *BuilderM) Where(query string, args ...any) *BuilderM
// Limit set limit
func (b *BuilderM) Limit(limit int) *BuilderM
// Page return paginated elements using Limit for specific page
func (b *BuilderM) Page(pageNumber int) *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 ctx
func (b *BuilderM) Context(ctx context.Context) *BuilderM
// Debug print prepared statement and values for this operation
func (b *BuilderM) Debug() *BuilderM
// All get all data
func (b *BuilderM) All() ([]map[string]any, error)
// One get single row
func (b *BuilderM) One() (map[string]any, error)
// Insert add row to a table using input map, and return PK of the inserted row
func (b *BuilderM) Insert(rowData map[string]any) (int, error)
// InsertR add row to a table using input map, and return the inserted row
func (b *BuilderM) InsertR(rowData map[string]any) (map[string]any, error)
// BulkInsert insert many row at the same time in one query
func (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(query string, 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 db
func (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 them
func (b *BuilderM) AddRelated(relatedTable string, whereRelatedTable string, whereRelatedArgs ...any) (int, error)
// GetRelated used for many to many to get related classes to a student or related students to a class
func (b *BuilderM) GetRelated(relatedTable string, dest *[]map[string]any) error
// JoinRelated same as get, but it join data
func (b *BuilderM) JoinRelated(relatedTable string, dest *[]map[string]any) error
// DeleteRelated delete a relations many to many
func (b *BuilderM) DeleteRelated(relatedTable string, whereRelatedTable string, whereRelatedArgs ...any) (int, error)


Examples:

sliceMapStringAny,err := korm.Table("users")
							.Select("email","uuid")
							.OrderBy("-id")
							.Limit(PAGINATION_PER)
							.Page(1)
							.All()

// INSERT
uuid,_ := korm.GenerateUUID()
hashedPass,_ := argon.Hash("password") // github.com/kamalshkeir/argon

korm.Table("users").Insert(map[string]any{
	"uuid":uuid,
	"email":"test@example.com",
	 ...
})

//if using more than one db
korm.Database("dbNameHere").Table("tableName").Where("id = ? AND email = ?",1,"test@example.com").All() 

// where
Where("id = ? AND email = ?",1,"test@example.com") // this work
Where("id,email",1,"test@example.com") // and this work

korm.Table("tableName").Where("id = ? AND email = ?",1,"test@example.com").One() 

// delete
korm.Table("tableName").Where("id = ? AND email = ?",1,"test@example.com").Delete() 

// drop table
korm.Table("tableName").Drop()

// update
korm.Table("tableName").Where("id = ?",1).Set("email = ?","new@example.com") 
korm.Table("tableName").Where("id",1).Set("email","new@example.com") 

Dashboard defaults you can set

korm.PaginationPer      = 10
korm.DocsUrl           = "docs"
korm.EmbededDashboard   = false
korm.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 main

import (
	"github.com/kamalshkeir/lg"
	"github.com/kamalshkeir/ksmux"
	"github.com/kamalshkeir/korm"
	"github.com/kamalshkeir/sqlitedriver"
)

func main() {
	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 certificates
	serverBus.RunTLS(cert string, certKey string)

	// OR generate certificates let's encrypt for a domain name, check https://github.com/kamalshkeir/ksbus for more infos
	serverBus.RunAutoTLS(subDomains ...string)
}

Then create admin user to connect to the dashboard

go run main.go shell

createsuperuser

Then you can visit /admin

Auth middleware example

func main() {
	err := korm.New(korm.SQLITE, "db", sqlitedriver.Use())
	if lg.CheckError(err) {
		return
	}
	defer korm.Shutdown()
	
	srv := korm.WithDashboard("localhost:9313")
	korm.WithShell()
	lg.Printfs("mgrunning on http://localhost:9313\n")
	app := srv.App

	app.Get("/", korm.Auth(func(c *ksmux.Context) { // work with korm.Admin also
		// c.IsAuthenticated also return bool
		if v, 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()
}

Example Admin Auth User and IsAuthenticated

{{define "admin_nav"}}
<header id="admin-header">
  <nav>
    <a href="/">
      <h1>KORM</h1>
    </a> 
    
    <ul>
        <li>
          <a {{if eq .Request.URL.Path "/" }}class="active"{{end}} href="/">Home</a>
        </li>

        <li>
          <a {{if contains .Request.URL.Path .admin_path }}class="active"{{end}} href="{{.admin_path}}">Admin</a>
        </li>

        {{if .IsAuthenticated}}
            <li>
              <a href="{{.admin_path}}/logout">Logout</a>
            </li>
            
            {{if .User.Email}}
              <li>
                <span>Hello {{.User.Email}}</span>
              </li>
            {{end}}
        {{end}}
    </ul>
  </nav>
</header>
{{end}}


Admin middlewares

// dash_middlewares.go
package korm

import (
	"context"
	"net/http"

	"github.com/kamalshkeir/aes"
	"github.com/kamalshkeir/ksmux"
)

var Auth = func(handler ksmux.Handler) ksmux.Handler {
	return func(c *ksmux.Context) {
		session, err := c.GetCookie("session")
		if err != nil || session == "" {
			// NOT AUTHENTICATED
			c.DeleteCookie("session")
			handler(c)
			return
		}
		session, err = aes.Decrypt(session)
		if err != nil {
			handler(c)
			return
		}
		// Check session
		user, err := Model[User]().Where("uuid = ?", session).One()
		if err != nil {
			// session fail
			handler(c)
			return
		}

		// AUTHENTICATED AND FOUND IN DB
		c.SetKey("korm-user", user)
		handler(c)
	}
}

var Admin = func(handler ksmux.Handler) ksmux.Handler {
	return func(c *ksmux.Context) {
		session, err := c.GetCookie("session")
		if err != nil || session == "" {
			// NOT AUTHENTICATED
			c.DeleteCookie("session")
			c.Status(http.StatusTemporaryRedirect).Redirect(adminPathNameGroup + "/login")
			return
		}
		session, err = aes.Decrypt(session)
		if err != nil {
			c.Status(http.StatusTemporaryRedirect).Redirect(adminPathNameGroup + "/login")
			return
		}
		user, err := Model[User]().Where("uuid = ?", session).One()

		if err != nil {
			// AUTHENTICATED BUT NOT FOUND IN DB
			c.Status(http.StatusTemporaryRedirect).Redirect(adminPathNameGroup + "/login")
			return
		}

		// Not admin
		if !user.IsAdmin {
			c.Status(403).Text("Middleware : Not allowed to access this page")
			return
		}
		c.SetKey("korm-user", user)
		handler(c)
	}
}

var BasicAuth = func(handler ksmux.Handler) ksmux.Handler {
	return ksmux.BasicAuth(handler, BASIC_AUTH_USER, BASIC_AUTH_PASS)
}

Example With Bus between 2 KORM

KORM 1:

package main

import (
	"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"
)

func main() {
	err := korm.New(korm.SQLITE,"db1", sqlitedriver.Use())
	if lg.CheckError(err) {return}

	korm.WithShell()
	serverBus := korm.WithBus(ksbus.ServerOpts{
		ID              string
		Address         string
		Path            string
		OnWsClose       func(connID string)
		OnDataWS        func(data map[string]any, conn *ws.Conn, originalRequest *http.Request) error
		OnServerData    func(data any, conn *ws.Conn)
		OnId            func(data map[string]any)
		OnUpgradeWs     func(r *http.Request) bool
		WithOtherRouter *ksmux.Router
		WithOtherBus    *Bus
	})
	// handler authentication	
	korm.BeforeDataWS(func(data map[string]any, conn *ws.Conn, originalRequest *http.Request) bool {
		lg.Info("handle authentication here")
		return true
	})
	// handler data from other KORM
	korm.BeforeServersData(func(data any, conn *ws.Conn) {
		lg.Info("recv orm:", "data", data)
	})

	// built in router to the bus, check it at https://github.com/kamalshkeir/ksbus
	serverBus.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 certificates
	serverBus.RunTLS(addr string, cert string, certKey string)
	// OR generate certificates let's encrypt for a domain name, check https://github.com/kamalshkeir/ksbus for more details
	serverBus.RunAutoTLS(domainName string, subDomains ...string)
}

KORM 2:

package main

import (
	"net/http"

	"github.com/kamalshkeir/lg"
	"github.com/kamalshkeir/ksmux"
	"github.com/kamalshkeir/ksmux/ws"
	"github.com/kamalshkeir/korm"
	"github.com/kamalshkeir/sqlitedriver"
)

func main() {
	err := korm.New(korm.SQLITE,"db2",sqlitedriver.Use())
	if lg.CheckError(err) {return}

	korm.WithShell() // if dashboard used, this line should be after it
	serverBus := korm.WithBus(ksbus.ServerOpts{
		ID              string
		Address         string
		Path            string
		OnWsClose       func(connID string)
		OnDataWS        func(data map[string]any, conn *ws.Conn, originalRequest *http.Request) error
		OnServerData    func(data any, conn *ws.Conn)
		OnId            func(data map[string]any)
		OnUpgradeWs     func(r *http.Request) bool
		WithOtherRouter *ksmux.Router
		WithOtherBus    *Bus
	})

	korm.BeforeServersData(func(data any, conn *ws.Conn) {
        lg.Info("recv", "data", data)
	})

	// built in router to the bus, check it at https://github.com/kamalshkeir/ksbus
	serverBus.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 Bus
	serverBus.Run("localhost:9314")

	// OR run https if you have certificates
	serverBus.RunTLS(addr string, cert string, certKey string)

	// OR generate certificates let's encrypt for a domain name, check https://github.com/kamalshkeir/ksbus for more infos
	serverBus.RunAutoTLS(domainName string, subDomains ...string)
}

Example generated tag

// generated example using concatination and length
type TestUser struct {
	Id        *uint   `korm:"pk"`
	Uuid      string  `korm:"size:40;iunique"`
	Email     *string `korm:"size:100;iunique"`
	Gen       string  `korm:"size:250;generated: concat(uuid,'working',len(password))"`
	Password  string
	IsAdmin   *bool
	CreatedAt time.Time `korm:"now"`
	UpdatedAt time.Time `korm:"update"`
}

func TestGeneratedAs(t *testing.T) {
	u, err := Model[TestUser]().Limit(3).All()
	if err != nil {
		t.Error(err)
	}
	if len(u) != 3 {
		t.Error("len not 20")
	}
	if u[0].Gen != u[0].Uuid+"working"+fmt.Sprintf("%d", len(u[0].Password)) {
		t.Error("generated not working:", u[0].Gen)
	}
}

Example concat and len from korm_test.go

// Where example
func TestConcatANDLen(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)
	if err != nil {
		t.Error(err)
	}
	if len(groupes) != 1 || groupes[0].Name != "admin" {
		t.Error("len(groupes) != 1 , got: ", groupes)
	}
}

Router/Mux

Learn more about Ksmux

func main() {
	err := korm.New(korm.SQLITE, "db", sqlitedriver.Use())
	if err != nil {
		log.Fatal(err)
	}

	serverBus := korm.WithDashboard("localhost:9313")
	korm.WithShell()
	mux := serverBus.App
	// add global middlewares
	mux.Use((midws ...func(http.Handler) http.Handler))
	...
}

Pprof


serverBus := korm.WithDashboard("localhost:9313")
// or srv := korm.WithBus()
serverBus.WithPprof(path ...string) // path is 'debug' by default

will enable:
	- /debug/pprof
	- /debug/profile
	- /debug/heap
	- /debug/trace

To execute profile cpu: go tool pprof -http=":8000" pprofbin http://localhost:9313/debug/profile?seconds=18 To execute profile memory: go tool pprof -http=":8000" pprofbin http://localhost:9313/debug/heap?seconds=18 To execute generate trace: go to endpoint http://localhost:9313/debug/trace?seconds=18 from browser , this will download the trace of 18 seconds Then to see the trace : go tool trace path/to/trace

Metrics Prometheus

// or srv := korm.WithBus()
//srv.WithMetrics(httpHandler http.Handler, path ...string) path default to 'metrics'
srv.WithMetrics(promhttp.Handler())

will enable:
	- /metrics

Logs middleware

// 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 send
srv.App.Use(ksmux.Logs(func(method, path, remote string, status int, took time.Duration) {
	// save somewhere
}))

will enable:
	- /metrics

Hooks

korm.OnInsert(func(database, table string, data map[string]any) error {
	fmt.Println("inserting into", database, table, data)
	// if error returned, it will not insert
	return nil
})

korm.OnSet(func(database, table string, data map[string]any) error {
	fmt.Println("set into", database, table, data)
	return nil
})

korm.OnDelete(func(database, table, query string, args ...any) error {})

korm.OnDrop(func(database, table string) error {})

Python bus client example

pip install ksbus==1.1.0
# if it doesn't work , execute it again 
from ksbus import Bus


# onOpen callback that let you know when connection is ready, it take the bus as param
def OnOpen(bus):
    print("connected")
    # bus.autorestart=True
    # Publish publish to topic
    bus.Publish("top", {
        "data": "hello from python"
    })
    # Subscribe, it also return the subscription
    bus.Subscribe("python", pythonTopicHandler)
    # SendToNamed publish to named topic
    bus.SendToNamed("top:srv", {
        "data": "hello again from python"
    })
    # bus.Unsubscribe("python")
    print("finish everything")


# pythonTopicHandler handle topic 'python'
def pythonTopicHandler(data, subs):
    print("recv on topic python:", data)
    # Unsubscribe
    #subs.Unsubscribe()

if __name__ == "__main__":
    Bus("localhost:9313", onOpen=OnOpen) # blocking
    print("prorgram exited")

ManyToMany Relationships Example

type Class struct {
	Id          uint   `korm:"pk"`
	Name        string `korm:"size:100"`
	IsAvailable bool
	CreatedAt   time.Time `korm:"now"`
}

type Student struct {
	Id        uint      `korm:"pk"`
	Name      string    `korm:"size:100"`
	CreatedAt time.Time `korm:"now"`
}

// migrate
func migrate() {
	err := korm.AutoMigrate[Class]("classes")
	if lg.CheckError(err) {
		return
	}
	err = korm.AutoMigrate[Student]("students")
	if lg.CheckError(err) {
		return
	}
	err = korm.ManyToMany("classes", "students")
	if lg.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 struct
std := []Student{}
err = korm.Model[Class]().Where("name = ?", "Math").Select("name").OrderBy("-name").Limit(1).GetRelated("students", &std)

// get related to map
std := []map[string]any{}
err = korm.Table("classes").Where("name = ?", "Math").Select("name").OrderBy("-name").Limit(1).GetRelated("students", &std)

// join related to map
std := []map[string]any{}
err = korm.Table("classes").Where("name = ?", "Math").JoinRelated("students", &std)

// join related to strcu
cu := []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")

Swagger documentation

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(embeded embed.FS, dirPath, korm.BasicAuth)
// dirPath default to 'assets/static/docs' if empty

Interactive shell

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 tables in 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 this help message

Example, not required, Load config from env directly to struct using Kenv

import "github.com/kamalshkeir/kenv"

type EmbedS struct {
	Static    bool `kenv:"EMBED_STATIC|false"`
	Templates bool `kenv:"EMBED_TEMPLATES|false"`
}

type GlobalConfig struct {
	Host       string `kenv:"HOST|localhost"` // DEFAULT to 'localhost': if HOST not found in env
	Port       string `kenv:"PORT|9313"`
	Embed 	   EmbedS
	Db struct {
		Name     string `kenv:"DB_NAME|db"` // NOT REQUIRED: if DB_NAME not found, defaulted to 'db'
		Type     string `kenv:"DB_TYPE"` // REEQUIRED: this env var is required, you will have error if empty
		DSN      string `kenv:"DB_DSN|"` // NOT REQUIRED: if DB_DSN not found it's not required, it's ok to stay empty
	}
	Smtp struct {
		Email string `kenv:"SMTP_EMAIL|"`
		Pass  string `kenv:"SMTP_PASS|"`
		Host  string `kenv:"SMTP_HOST|"`
		Port  string `kenv:"SMTP_PORT|"`
	}
	Profiler   bool   `kenv:"PROFILER|false"`
	Docs       bool   `kenv:"DOCS|false"`
	Logs       bool   `kenv:"LOGS|false"`
	Monitoring bool   `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

Example nested or embeded structs

package main

import (
	"fmt"
	"time"

	"github.com/kamalshkeir/lg"
	"github.com/kamalshkeir/korm"
	"github.com/kamalshkeir/sqlitedriver"
)

type Class struct {
	Id       uint `korm:"pk"`
	Name     string
	Students []Student
}

type Student struct {
	Id      uint `korm:"pk"`
	Name    string
	Class   uint `korm:"fk:classes.id:cascade:cascade"`
	Classes Class
}

func main() {
	err := korm.New(korm.SQLITE, "db", sqlitedriver.Use())
	if lg.CheckError(err) {
		return
	}
	defer korm.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 slice
	studentsChan := []chan Student{make(chan Student)}
	go func() {
		for s := range studentsChan[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 structs
	classes := []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 := range classes {
		fmt.Println("class:", s)
	}
	fmt.Println()

	// // not nested, only remove second arg true from Scan method
	students := []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 := range students {
		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(&times).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]

Benchmark vs Tarantool, Pgx, Gorm

https://github.com/kamalshkeir/korm-vs-gorm-vs-tarantool-vs-pgx

Benchmarks vs Gorm

goos: windows
goarch: amd64
pkg: github.com/kamalshkeir/korm/benchmarks
cpu: Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz

To execute these benchmarks on your machine, very easy :

type TestTable struct {
	Id        uint `korm:"pk"`
	Email     string
	Content   string
	Password  string
	IsAdmin   bool
	CreatedAt time.Time `korm:"now"`
	UpdatedAt time.Time `korm:"update"`
}

type TestTableGorm struct {
	Id        uint `gorm:"primarykey"`
	Email     string
	Content   string
	Password  string
	IsAdmin   bool
	CreatedAt time.Time
	UpdatedAt time.Time
}
////////////////////////////////////////////  query 7000 rows  //////////////////////////////////////////////
BenchmarkGetAllS_GORM-4                       19          56049832 ns/op        12163316 B/op     328790 allocs/op
BenchmarkGetAllS-4                       2708934               395.3 ns/op           224 B/op          1 allocs/op
BenchmarkGetAllM_GORM-4                       18          62989567 ns/op        13212278 B/op     468632 allocs/op
BenchmarkGetAllM-4                       4219461               273.5 ns/op           224 B/op          1 allocs/op
BenchmarkGetRowS_GORM-4                    12188             96988 ns/op            5930 B/op        142 allocs/op
BenchmarkGetRowS-4                       1473164               805.1 ns/op           336 B/op          7 allocs/op
BenchmarkGetRowM_GORM-4                    11402            101638 ns/op            7408 B/op        203 allocs/op
BenchmarkGetRowM-4                       1752652               671.9 ns/op           336 B/op          7 allocs/op
BenchmarkPagination10_GORM-4                7714            153304 ns/op           19357 B/op        549 allocs/op
BenchmarkPagination10-4                  1285722               934.5 ns/op           400 B/op          7 allocs/op
BenchmarkPagination100_GORM-4               1364            738934 ns/op          165423 B/op       4704 allocs/op
BenchmarkPagination100-4                 1278724               956.5 ns/op           400 B/op          7 allocs/op
BenchmarkQueryS-4                        5781499               207.7 ns/op             4 B/op          1 allocs/op
BenchmarkQueryM-4                        4643155               227.2 ns/op             4 B/op          1 allocs/op
BenchmarkGetAllTables-4                 47465865                25.48 ns/op            0 B/op          0 allocs/op
BenchmarkGetAllColumns-4                23657019                42.82 ns/op            0 B/op          0 allocs/op
////////////////////////////////////////////  query 5000 rows  //////////////////////////////////////////////
BenchmarkGetAllS_GORM-4                       24          43247546 ns/op         8796840 B/op     234784 allocs/op
BenchmarkGetAllS-4                       2854401               426.8 ns/op           224 B/op          1 allocs/op
BenchmarkGetAllM_GORM-4                       24          46329242 ns/op         9433050 B/op     334631 allocs/op
BenchmarkGetAllM-4                       4076317               283.4 ns/op           224 B/op          1 allocs/op
BenchmarkGetRowS_GORM-4                    11445            101107 ns/op            5962 B/op        142 allocs/op
BenchmarkGetRowS-4                       1344831               848.4 ns/op           336 B/op          7 allocs/op
BenchmarkGetRowM_GORM-4                    10000            100969 ns/op            7440 B/op        203 allocs/op
BenchmarkGetRowM-4                       1721742               688.5 ns/op           336 B/op          7 allocs/op
BenchmarkPagination10_GORM-4                7500            156208 ns/op           19423 B/op        549 allocs/op
BenchmarkPagination10-4                  1253757               952.3 ns/op           400 B/op          7 allocs/op
BenchmarkPagination100_GORM-4               1564            749408 ns/op          165766 B/op       4704 allocs/op
BenchmarkPagination100-4                 1236270               957.5 ns/op           400 B/op          7 allocs/op
BenchmarkGetAllTables-4                 44399386                25.43 ns/op            0 B/op          0 allocs/op
BenchmarkGetAllColumns-4                27906392                41.45 ns/op            0 B/op          0 allocs/op
////////////////////////////////////////////  query 1000 rows  //////////////////////////////////////////////
BenchmarkGetAllS_GORM-4                      163           6766871 ns/op         1683919 B/op      46735 allocs/op
BenchmarkGetAllS-4                       2882660               399.0 ns/op           224 B/op          1 allocs/op
BenchmarkGetAllM_GORM-4                      140           8344988 ns/op         1886922 B/op      66626 allocs/op
BenchmarkGetAllM-4                       3826730               296.5 ns/op           224 B/op          1 allocs/op
BenchmarkGetRowS_GORM-4                    11940             97725 ns/op            5935 B/op        142 allocs/op
BenchmarkGetRowS-4                       1333258               903.0 ns/op           336 B/op          7 allocs/op
BenchmarkGetRowM_GORM-4                    10000            106079 ns/op            7408 B/op        203 allocs/op
BenchmarkGetRowM-4                       1601274               748.2 ns/op           336 B/op          7 allocs/op
BenchmarkPagination10_GORM-4                7534            159991 ns/op           19409 B/op        549 allocs/op
BenchmarkPagination10-4                  1153982              1022 ns/op             400 B/op          7 allocs/op
BenchmarkPagination100_GORM-4               1468            766269 ns/op          165876 B/op       4705 allocs/op
BenchmarkPagination100-4                 1000000              1016 ns/op             400 B/op          7 allocs/op
BenchmarkGetAllTables-4                 56200297                25.36 ns/op            0 B/op          0 allocs/op
BenchmarkGetAllColumns-4                25478679                41.30 ns/op            0 B/op          0 allocs/op
////////////////////////////////////////////  query 300 rows  //////////////////////////////////////////////
BenchmarkGetAllS_GORM-4                      558           2046830 ns/op          458475 B/op      13823 allocs/op
BenchmarkGetAllS-4                       2798872               411.5 ns/op           224 B/op          1 allocs/op
BenchmarkGetAllM_GORM-4                      428           2605646 ns/op          567011 B/op      19721 allocs/op
BenchmarkGetAllM-4                       4093662               287.9 ns/op           224 B/op          1 allocs/op
BenchmarkGetRowS_GORM-4                    12182             97764 ns/op            5966 B/op        142 allocs/op
BenchmarkGetRowS-4                       1347084               886.4 ns/op           336 B/op          7 allocs/op
BenchmarkGetRowM_GORM-4                    10000            105311 ns/op            7440 B/op        203 allocs/op
BenchmarkGetRowM-4                       1390363               780.0 ns/op           336 B/op          7 allocs/op
BenchmarkPagination10_GORM-4                7502            155949 ns/op           19437 B/op        549 allocs/op
BenchmarkPagination10-4                  1000000              1046 ns/op             400 B/op          7 allocs/op
BenchmarkPagination100_GORM-4               1479            779700 ns/op          165679 B/op       4705 allocs/op
BenchmarkPagination100-4                 1000000              1054 ns/op             400 B/op          7 allocs/op
BenchmarkGetAllTables-4                 52255704                26.00 ns/op            0 B/op          0 allocs/op
BenchmarkGetAllColumns-4                29292368                42.09 ns/op            0 B/op          0 allocs/op
////////////////////////////////////////////    MONGO       //////////////////////////////////////////////
BenchmarkGetAllS-4               3121384               385.6 ns/op           224 B/op          1 allocs/op
BenchmarkGetAllM-4               4570059               264.2 ns/op           224 B/op          1 allocs/op
BenchmarkGetRowS-4               1404399               866.6 ns/op           336 B/op          7 allocs/op
BenchmarkGetRowM-4               1691026               722.6 ns/op           336 B/op          7 allocs/op
BenchmarkGetAllTables-4         47424489                25.34 ns/op            0 B/op          0 allocs/op
BenchmarkGetAllColumns-4        27039632                42.22 ns/op            0 B/op          0 allocs/op
//////////////////////////////////////////////////////////////////////////////////////////////////////////

Available Tags by struct field type:

String Field:

Without parameter                 With parameter                           
*  	text (create column as TEXT not VARCHAR)
*  	notnull
*  	unique
*   iunique // insensitive unique
*  	index, +index, index+ (INDEX ascending)
*  	index-, -index (INDEX descending)
*  	default (DEFAULT '')
* 	default:'any' (DEFAULT 'any')
*	mindex:...
* 	uindex:username,Iemail // CREATE UNIQUE INDEX ON users (username,LOWER(email)) 
	// 	email is lower because of 'I' meaning Insensitive for email
* 	fk:...
* 	size:50  (VARCHAR(50))
* 	check:...

Int, Uint, Int64, Uint64 Fields:

Without parameter                
*   -  			 (To Ignore a field)
*   autoinc, pk  (PRIMARY KEY)
*   notnull      (NOT NULL)
*  	index, +index, index+ (CREATE INDEX ON COLUMN)
*  	index-, -index(CREATE INDEX DESC ON COLUMN)     
*   unique 		 (CREATE UNIQUE INDEX ON COLUMN) 
*   default		 (DEFAULT 0)
With parameter                           
Available 'on_delete' and 'on_update' options: cascade,(donothing,noaction),(setnull,null),(setdefault,default)

*   fk:{table}.{column}:{on_delete}:{on_update} 
*   check: len(to_check) > 10 ; check: is_used=true (You can chain checks or keep it in the same CHECK separated by AND)
*   mindex: first_name, last_name (CREATE MULTI INDEX ON COLUMN + first_name + last_name)
*   uindex: first_name, last_name (CREATE MULTI UNIQUE INDEX ON COLUMN + first_name + last_name) 
*   default:5 (DEFAULT 5)

Bool : bool is INTEGER NOT NULL checked between 0 and 1 (in order to be consistent accross sql dialects)

Without parameter                 With parameter                           
*  	index, +index, index+ (CREATE INDEX ON COLUMN)
*  	index-, -index(CREATE INDEX DESC ON COLUMN)  
*   default (DEFAULT 0)
*   default:1 (DEFAULT 1)
*   mindex:...
*   fk:...

time.Time :

Without parameter                 With parameter
*  	index, +index, index+ (CREATE INDEX ON COLUMN)
*  	index-, -index(CREATE INDEX DESC ON COLUMN)  
*   unique
*   now (NOT NULL and defaulted to current unix timestamp)
*   update (NOT NULL DEFAULT UNIX_TIMESTAMP ON UPDATE UNIX_TIMESTAMP)
*   fk:...
*   check:...
*   uindex:...

Float64 :

Without parameter                 With parameter                           
*   notnull
*  	index, +index, index+ (CREATE INDEX ON COLUMN)
*  	index-, -index(CREATE INDEX DESC ON COLUMN)  
*   unique
*   default
*   default:...
*   fk:...
*   mindex:...
*   uindex:...
*   check:...

JSON

type JsonOption struct {
	As       string
	Dialect  string
	Database string
	Params   []any
}
func JSON_EXTRACT(dataJson string, opt ...JsonOption) string
func JSON_REMOVE(dataJson string, opt ...JsonOption) string
func JSON_SET(dataJson string, opt ...JsonOption) string
func JSON_ARRAY(values []any, as string, dialect ...string) string
func JSON_OBJECT(values []any, as string, dialect ...string) string
func JSON_CAST(value string, as string, dialect ...string) string

// create query json
q := 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 data

var data []map[string]any
err := korm.To(&data).Query("SELECT " + q)
lg.CheckError(err)

fmt.Println("data=", data) // data= [map[data:[3,2]]]

🔗 Links

portfolio linkedin


Licence

Licence BSD-3

Documentation ¶

Index ¶

Constants ¶

This section is empty.

Variables ¶

View Source
var (
	ErrNoConnection = errors.New("no connection")
	ErrNoData       = errors.New("no data")
)
View Source
var (
	BASIC_AUTH_USER = "notset"
	BASIC_AUTH_PASS = "testnotsetbutwaititshouldbeset"
)
View Source
var (

	// Debug when true show extra useful logs for queries executed for migrations and queries statements
	Debug = false
	// FlushCacheEvery execute korm.FlushCache() every 10 min by default, you should not worry about it, but useful that you can change it
	FlushCacheEvery = 10 * time.Minute
	// MaxOpenConns set max open connections for db pool
	MaxOpenConns = 50
	// MaxIdleConns set max idle connections for db pool
	MaxIdleConns = 30
	// MaxLifetime set max lifetime for a connection in the db pool
	MaxLifetime = 30 * time.Minute
	// MaxIdleTime set max idletime for a connection in the db pool
	MaxIdleTime = 30 * time.Minute
)
View Source
var (
	ErrTableNotFound = errors.New("unable to find tableName")
	ErrBigData       = kmap.ErrLargeData
)
View Source
var Admin = func(handler ksmux.Handler) ksmux.Handler {
	return func(c *ksmux.Context) {
		session, err := c.GetCookie("session")
		if err != nil || session == "" {

			c.DeleteCookie("session")
			c.Status(http.StatusTemporaryRedirect).Redirect(adminPathNameGroup + "/login")
			return
		}
		session, err = aes.Decrypt(session)
		if err != nil {
			c.Status(http.StatusTemporaryRedirect).Redirect(adminPathNameGroup + "/login")
			return
		}
		user, err := Model[User]().Where("uuid = ?", session).One()

		if err != nil {

			c.Status(http.StatusTemporaryRedirect).Redirect(adminPathNameGroup + "/login")
			return
		}

		if !user.IsAdmin {
			c.Status(403).Text("Middleware : Not allowed to access this page")
			return
		}
		c.SetKey("korm-user", user)
		handler(c)
	}
}
View Source
var AllModelsGet = func(c *ksmux.Context) {
	model := c.Param("model")
	if model == "" {
		c.Json(map[string]any{
			"error": "Error: No model given in params",
		})
		return
	}

	dbMem, _ := GetMemoryDatabase(defaultDB)
	if dbMem == nil {
		lg.ErrorC("unable to find db in mem", "db", defaultDB)
		dbMem = &databases[0]
	}
	idString := "id"
	var t *TableEntity
	for i, tt := range dbMem.Tables {
		if tt.Name == model {
			idString = tt.Pk
			t = &dbMem.Tables[i]
		}
	}

	rows, err := Table(model).Database(defaultDB).OrderBy("-" + idString).Limit(paginationPer).Page(1).All()
	if err != nil {
		rows, err = Table(model).Database(defaultDB).All()
		if err != nil {
			if err != ErrNoData {
				c.Status(404).Error("Unable to find this model")
				return
			}
		}
	}
	dbCols, cols := GetAllColumnsTypes(model)
	mmfkeysModels := map[string][]map[string]any{}
	mmfkeys := map[string][]any{}
	if t != nil {
		for _, fkey := range t.Fkeys {
			spFrom := strings.Split(fkey.FromTableField, ".")
			if len(spFrom) == 2 {
				spTo := strings.Split(fkey.ToTableField, ".")
				if len(spTo) == 2 {
					q := "select * from " + spTo[0] + " order by " + spTo[1]
					mm := []map[string]any{}
					err := To(&mm).Query(q)
					if !lg.CheckError(err) {
						ress := []any{}
						for _, res := range mm {
							ress = append(ress, res[spTo[1]])
						}
						if len(ress) > 0 {
							mmfkeys[spFrom[1]] = ress
							mmfkeysModels[spFrom[1]] = mm
							for _, v := range mmfkeysModels[spFrom[1]] {
								for i, vv := range v {
									if vvStr, ok := vv.(string); ok {
										if len(vvStr) > TruncatePer {
											v[i] = vvStr[:TruncatePer] + "..."
										}
									}
								}
							}
						}
					} else {
						lg.ErrorC("error:", "q", q, "spTo", spTo)
					}
				}
			}
		}
	} else {
		idString = cols[0]
	}

	if dbMem != nil {
		data := map[string]any{
			"dbType":         dbMem.Dialect,
			"table":          model,
			"rows":           rows,
			"dbcolumns":      dbCols,
			"pk":             idString,
			"fkeys":          mmfkeys,
			"fkeysModels":    mmfkeysModels,
			"columnsOrdered": cols,
		}
		if t != nil {
			data["columns"] = t.ModelTypes
		} else {
			data["columns"] = dbCols
		}
		c.Html("admin/admin_single_table.html", data)
	} else {
		lg.ErrorC("table not found", "table", model)
		c.Status(404).Error("Unable to find this model")
	}
}
View Source
var AllModelsSearch = func(c *ksmux.Context) {
	model := c.Param("model")
	if model == "" {
		c.Json(map[string]any{
			"error": "Error: No model given in params",
		})
		return
	}

	body := c.BodyJson()

	blder := Table(model).Database(defaultDB)
	if query, ok := body["query"]; ok {
		if v, ok := query.(string); ok {
			if v != "" {
				blder.Where(v)
			}
		} else {
			c.Json(map[string]any{
				"error": "Error: No query given in body",
			})
			return
		}
	}

	oB := ""
	t, err := GetMemoryTable(model, defaultDB)
	if lg.CheckError(err) {
		c.Json(map[string]any{
			"error": err,
		})
		return
	}

	mmfkeysModels := map[string][]map[string]any{}
	mmfkeys := map[string][]any{}
	for _, fkey := range t.Fkeys {
		spFrom := strings.Split(fkey.FromTableField, ".")
		if len(spFrom) == 2 {
			spTo := strings.Split(fkey.ToTableField, ".")
			if len(spTo) == 2 {
				q := "select * from " + spTo[0] + " order by " + spTo[1]
				mm := []map[string]any{}
				err := To(&mm).Query(q)
				if !lg.CheckError(err) {
					ress := []any{}
					for _, res := range mm {
						ress = append(ress, res[spTo[1]])
					}
					if len(ress) > 0 {
						mmfkeys[spFrom[1]] = ress
						mmfkeysModels[spFrom[1]] = mm
						for _, v := range mmfkeysModels[spFrom[1]] {
							for i, vv := range v {
								if vvStr, ok := vv.(string); ok {
									if len(vvStr) > TruncatePer {
										v[i] = vvStr[:TruncatePer] + "..."
									}
								}
							}
						}
					}
				} else {
					lg.ErrorC("error:", "q", q, "spTo", spTo)
				}
			}
		}
	}

	if oB != "" {
		blder.OrderBy(oB)
	} else {
		blder.OrderBy("-" + t.Pk)
	}

	pageNum := 1
	if v, ok := body["page_num"]; ok {
		if pn, ok := v.(string); ok {
			if p, err := strconv.Atoi(pn); err == nil {
				pageNum = p
			}
		}
	}
	blder.Limit(paginationPer).Page(pageNum)

	data, err := blder.All()
	if err != nil {
		if err != ErrNoData {
			c.Status(http.StatusBadRequest).Json(map[string]any{
				"error": err.Error(),
			})
			return
		}
		data = []map[string]any{}
	}

	// Get total count for pagination
	var total int64
	var totalRows []int64
	query := "SELECT COUNT(*) FROM " + model
	if v, ok := body["query"]; ok {
		if vStr, ok := v.(string); ok && vStr != "" {
			query += " WHERE " + vStr
		}
	}
	err = To(&totalRows).Query(query)
	if err == nil {
		total = totalRows[0]
	}

	c.Json(map[string]any{
		"table":       model,
		"rows":        data,
		"cols":        t.Columns,
		"types":       t.ModelTypes,
		"fkeys":       mmfkeys,
		"fkeysModels": mmfkeysModels,
		"total":       total,
	})
}
View Source
var Auth = func(handler ksmux.Handler) ksmux.Handler {
	return func(c *ksmux.Context) {
		session, err := c.GetCookie("session")
		if err != nil || session == "" {

			c.DeleteCookie("session")
			handler(c)
			return
		}
		session, err = aes.Decrypt(session)
		if err != nil {
			handler(c)
			return
		}

		user, err := Model[User]().Where("uuid = ?", session).One()
		if err != nil {

			handler(c)
			return
		}

		c.SetKey("korm-user", user)
		handler(c)
	}
}
View Source
var BasicAuth = func(handler ksmux.Handler) ksmux.Handler {
	return ksmux.BasicAuth(handler, BASIC_AUTH_USER, BASIC_AUTH_PASS)
}
View Source
var BulkDeleteRowPost = func(c *ksmux.Context) {
	data := struct {
		Ids   []uint
		Table string
	}{}
	if lg.CheckError(c.BodyStruct(&data)) {
		c.Error("BAD REQUEST")
		return
	}
	idString := "id"
	t, err := GetMemoryTable(data.Table, defaultDB)
	if err != nil {
		c.Status(404).Json(map[string]any{
			"error": "table not found",
		})
		return
	}
	if t.Pk != "" && t.Pk != "id" {
		idString = t.Pk
	}
	_, err = Table(data.Table).Database(defaultDB).Where(idString+" IN (?)", data.Ids).Delete()
	if lg.CheckError(err) {
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": err.Error(),
		})
		return
	}
	c.Json(map[string]any{
		"success": "DELETED WITH SUCCESS",
		"ids":     data.Ids,
	})
}
View Source
var ClearTraces = func(c *ksmux.Context) {
	ksmux.ClearTraces()
	c.Success("traces cleared")
}
View Source
var CreateModelView = func(c *ksmux.Context) {
	data, files := c.ParseMultipartForm()

	model := data["table"][0]
	m := map[string]any{}
	for key, val := range data {
		switch key {
		case "table":
			continue
		case "uuid":
			if v := m[key]; v == "" {
				m[key] = GenerateUUID()
			} else {
				m[key] = val[0]
			}
		case "password":
			hash, _ := argon.Hash(val[0])
			m[key] = hash
		case "email":
			if !IsValidEmail(val[0]) {
				c.Json(map[string]any{
					"error": "email not valid",
				})
				return
			}
			m[key] = val[0]
		case "pk":
			continue
		default:
			if key != "" && val[0] != "" && val[0] != "null" {
				m[key] = val[0]
			}
		}
	}
	inserted, err := Table(model).Database(defaultDB).InsertR(m)
	if err != nil {
		lg.ErrorC("CreateModelView error", "err", err)
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": err.Error(),
		})
		return
	}

	idString := "id"
	t, _ := GetMemoryTable(data["table"][0], defaultDB)
	if t.Pk != "" && t.Pk != "id" {
		idString = t.Pk
	}
	pathUploaded, formName, err := handleFilesUpload(files, data["table"][0], fmt.Sprintf("%v", inserted[idString]), c, idString)
	if err != nil {
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": err.Error(),
		})
		return
	}
	if len(pathUploaded) > 0 {
		inserted[formName[0]] = pathUploaded[0]
	}

	c.Json(map[string]any{
		"success":  "Done !",
		"inserted": inserted,
	})
}
View Source
var DashView = func(c *ksmux.Context) {
	ddd := map[string]any{
		"withRequestCounter": withRequestCounter,
		"stats":              GetStats(),
	}
	if withRequestCounter {
		ddd["requests"] = GetTotalRequests()
	}

	c.Html("admin/admin_index.html", ddd)
}
View Source
var DropTablePost = func(c *ksmux.Context) {
	data := c.BodyJson()
	if table, ok := data["table"]; ok && table != "" {
		if t, ok := data["table"].(string); ok {
			_, err := Table(t).Database(defaultDB).Drop()
			if lg.CheckError(err) {
				c.Status(http.StatusBadRequest).Json(map[string]any{
					"error": err.Error(),
				})
				return
			}
		} else {
			c.Status(http.StatusBadRequest).Json(map[string]any{
				"error": "expecting 'table' to be string",
			})
		}
	} else {
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": "missing 'table' in body request",
		})
	}
	c.Json(map[string]any{
		"success": fmt.Sprintf("table %s Deleted !", data["table"]),
	})
}
View Source
var ExportCSVView = func(c *ksmux.Context) {
	table := c.Param("table")
	if table == "" {
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": "no param table found",
		})
		return
	}
	data, err := Table(table).Database(defaultDB).All()
	lg.CheckError(err)
	var buff bytes.Buffer
	writer := csv.NewWriter(&buff)

	cols := []string{}
	tab, _ := GetMemoryTable(table, defaultDB)
	if len(tab.Columns) > 0 {
		cols = tab.Columns
	} else if len(data) > 0 {
		d := data[0]
		for k := range d {
			cols = append(cols, k)
		}
	}

	err = writer.Write(cols)
	lg.CheckError(err)
	for _, sd := range data {
		values := []string{}
		for _, k := range cols {
			switch vv := sd[k].(type) {
			case string:
				values = append(values, vv)
			case bool:
				if vv {
					values = append(values, "true")
				} else {
					values = append(values, "false")
				}
			case int:
				values = append(values, strconv.Itoa(vv))
			case int64:
				values = append(values, strconv.Itoa(int(vv)))
			case uint:
				values = append(values, strconv.Itoa(int(vv)))
			case time.Time:
				values = append(values, vv.String())
			default:
				values = append(values, fmt.Sprintf("%v", vv))
			}

		}
		err = writer.Write(values)
		lg.CheckError(err)
	}
	writer.Flush()
	c.Download(buff.Bytes(), table+".csv")
}
View Source
var ExportView = func(c *ksmux.Context) {
	table := c.Param("table")
	if table == "" {
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": "no param table found",
		})
		return
	}
	data, err := Table(table).Database(defaultDB).All()
	lg.CheckError(err)

	data_bytes, err := json.Marshal(data)
	lg.CheckError(err)

	c.Download(data_bytes, table+".json")
}
View Source
var GetLogsView = func(c *ksmux.Context) {
	parsed := make([]LogEntry, 0)
	if v := lg.GetLogs(); v != nil {
		for _, vv := range reverseSlice(v.Slice) {
			parsed = append(parsed, parseLogString(vv))
		}
	}
	c.Json(parsed)
}
View Source
var GetMetricsView = func(c *ksmux.Context) {
	metrics := GetSystemMetrics()
	c.Json(metrics)
}
View Source
var GetTraces = func(c *ksmux.Context) {
	dbtraces := GetDBTraces()
	if len(dbtraces) > 0 {
		for _, t := range dbtraces {
			sp, _ := ksmux.StartSpan(context.Background(), t.Query)
			sp.SetTag("query", t.Query)
			sp.SetTag("args", fmt.Sprint(t.Args))
			if t.Database != "" {
				sp.SetTag("database", t.Database)
			}
			sp.SetTag("duration", t.Duration.String())
			sp.SetDuration(t.Duration)
			sp.SetError(t.Error)
			sp.End()
		}
		ClearDBTraces()
	}

	traces := ksmux.GetTraces()
	traceList := make([]map[string]interface{}, 0)
	for traceID, spans := range traces {
		spanList := make([]map[string]interface{}, 0)
		for _, span := range spans {
			errorMsg := ""
			if span.Error() != nil {
				errorMsg = span.Error().Error()
			}
			spanList = append(spanList, map[string]interface{}{
				"id":         span.SpanID(),
				"parentID":   span.ParentID(),
				"name":       span.Name(),
				"startTime":  span.StartTime(),
				"endTime":    span.EndTime(),
				"duration":   span.Duration().String(),
				"tags":       span.Tags(),
				"statusCode": span.StatusCode(),
				"error":      errorMsg,
			})
		}
		traceList = append(traceList, map[string]interface{}{
			"traceID": traceID,
			"spans":   spanList,
		})
	}
	c.Json(traceList)
}
View Source
var ImportView = func(c *ksmux.Context) {

	table := c.Request.FormValue("table")
	if table == "" {
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": "no table !",
		})
		return
	}
	t, err := GetMemoryTable(table, defaultDB)
	if lg.CheckError(err) {
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": err.Error(),
		})
		return
	}

	fname, dataBytes, err := c.UploadFile("thefile", "backup", "json", "csv")
	if lg.CheckError(err) {
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": err.Error(),
		})
		return
	}
	isCsv := strings.HasSuffix(fname, ".csv")

	modelsOld, _ := Table(table).Database(defaultDB).All()
	if len(modelsOld) > 0 {
		modelsOldBytes, err := json.Marshal(modelsOld)
		if !lg.CheckError(err) {
			_ = os.MkdirAll(mediaDir+"/backup/", 0770)
			dst, err := os.Create(mediaDir + "/backup/" + table + "-" + time.Now().Format("2006-01-02") + ".json")
			lg.CheckError(err)
			defer dst.Close()
			_, err = dst.Write(modelsOldBytes)
			lg.CheckError(err)
		}
	}

	list_map := []map[string]any{}
	if isCsv {
		reader := csv.NewReader(bytes.NewReader(dataBytes))
		lines, err := reader.ReadAll()
		if lg.CheckError(err) {
			c.Status(http.StatusBadRequest).Json(map[string]any{
				"error": err.Error(),
			})
			return
		}

		for _, values := range lines {
			m := map[string]any{}
			for i := range values {
				m[t.Columns[i]] = values[i]
			}
			list_map = append(list_map, m)
		}
	} else {
		err := json.Unmarshal(dataBytes, &list_map)
		if lg.CheckError(err) {
			c.Status(http.StatusBadRequest).Json(map[string]any{
				"error": err.Error(),
			})
			return
		}
	}

	// create models in database
	var retErr []error
	for _, m := range list_map {
		_, err = Table(table).Database(defaultDB).Insert(m)
		if err != nil {
			retErr = append(retErr, err)
		}
	}
	if len(retErr) > 0 {
		c.Json(map[string]any{
			"success": "some data could not be added, " + errors.Join(retErr...).Error(),
		})
		return
	}

	c.Json(map[string]any{
		"success": "Import Done , you can see uploaded backups at ./" + mediaDir + "/backup folder",
	})
}
View Source
var LoginPOSTView = func(c *ksmux.Context) {
	requestData := c.BodyJson()
	email := requestData["email"]
	passRequest := requestData["password"]

	data, err := Table("users").Database(defaultDB).Where("email = ?", email).One()
	if err != nil {
		c.Status(http.StatusUnauthorized).Json(map[string]any{
			"error": err.Error(),
		})
		return
	}
	if data["email"] == "" || data["email"] == nil {
		c.Status(http.StatusNotFound).Json(map[string]any{
			"error": "User doesn not Exist",
		})
		return
	}
	if data["is_admin"] == int64(0) || data["is_admin"] == 0 || data["is_admin"] == false {
		c.Status(http.StatusForbidden).Json(map[string]any{
			"error": "Not Allowed to access this page",
		})
		return
	}

	if passDB, ok := data["password"].(string); ok {
		if pp, ok := passRequest.(string); ok {
			if !argon.Match(passDB, pp) {
				c.Status(http.StatusForbidden).Json(map[string]any{
					"error": "Wrong Password",
				})
				return
			} else {
				if uuid, ok := data["uuid"].(string); ok {
					uuid, err = aes.Encrypt(uuid)
					lg.CheckError(err)
					c.SetCookie("session", uuid)
					c.Json(map[string]any{
						"success": "U Are Logged In",
					})
					return
				}
			}
		}
	}
}
View Source
var LoginView = func(c *ksmux.Context) {
	c.Html("admin/admin_login.html", nil)
}
View Source
var LogoutView = func(c *ksmux.Context) {
	c.DeleteCookie("session")
	c.Status(http.StatusTemporaryRedirect).Redirect("/")
}
View Source
var LogsView = func(c *ksmux.Context) {
	d := map[string]any{
		"metrics": GetSystemMetrics(),
	}
	parsed := make([]LogEntry, 0)
	if v := lg.GetLogs(); v != nil {
		for _, vv := range reverseSlice(v.Slice) {
			parsed = append(parsed, parseLogString(vv))
		}
	}
	d["parsed"] = parsed
	c.Html("admin/admin_logs.html", d)
}
View Source
var ManifestView = func(c *ksmux.Context) {
	if embededDashboard {
		f, err := staticAndTemplatesFS[0].ReadFile(staticDir + "/manifest.json")
		if err != nil {
			lg.ErrorC("cannot embed manifest.json", "err", err)
			return
		}
		c.ServeEmbededFile("application/json; charset=utf-8", f)
	} else {
		c.ServeFile("application/json; charset=utf-8", staticDir+"/manifest.json")
	}
}
View Source
var (
	MaxDbTraces = 50
)
View Source
var OfflineView = func(c *ksmux.Context) {
	c.Text("<h1>YOUR ARE OFFLINE, check connection</h1>")
}
View Source
var RestartView = func(c *ksmux.Context) {
	if serverBus != nil {
		lg.CheckError(serverBus.App().Restart())
	}
}
View Source
var RobotsTxtView = func(c *ksmux.Context) {
	c.ServeFile("text/plain; charset=utf-8", "."+staticUrl+"/robots.txt")
}
View Source
var ServiceWorkerView = func(c *ksmux.Context) {
	if embededDashboard {
		f, err := staticAndTemplatesFS[0].ReadFile(staticDir + "/sw.js")
		if err != nil {
			lg.ErrorC("cannot embed sw.js", "err", err)
			return
		}
		c.ServeEmbededFile("application/javascript; charset=utf-8", f)
	} else {
		c.ServeFile("application/javascript; charset=utf-8", staticDir+"/sw.js")
	}
}
View Source
var TableGetAll = func(c *ksmux.Context) {
	model := c.Param("model")
	if model == "" {
		c.Json(map[string]any{
			"error": "Error: No model given in params",
		})
		return
	}
	dbMem, _ := GetMemoryDatabase(defaultDB)
	if dbMem == nil {
		lg.ErrorC("unable to find db in mem", "db", defaultDB)
		dbMem = &databases[0]
	}
	idString := "id"
	var t *TableEntity
	for i, tt := range dbMem.Tables {
		if tt.Name == model {
			idString = tt.Pk
			t = &dbMem.Tables[i]
		}
	}

	var body struct {
		Page int `json:"page"`
	}
	if err := c.BodyStruct(&body); lg.CheckError(err) {
		c.Error("something wrong happened")
		return
	}
	if body.Page == 0 {
		body.Page = 1
	}
	rows, err := Table(model).Database(defaultDB).OrderBy("-" + idString).Limit(paginationPer).Page(body.Page).All()
	if err != nil {
		if err != ErrNoData {
			c.Status(404).Error("Unable to find this model")
			return
		}
		rows = []map[string]any{}
	}

	// Get total count for pagination
	var total int64
	var totalRows []int64
	err = To(&totalRows).Query("SELECT COUNT(*) FROM " + model)
	if err == nil {
		total = totalRows[0]
	}

	dbCols, cols := GetAllColumnsTypes(model)
	mmfkeysModels := map[string][]map[string]any{}
	mmfkeys := map[string][]any{}
	if t != nil {
		for _, fkey := range t.Fkeys {
			spFrom := strings.Split(fkey.FromTableField, ".")
			if len(spFrom) == 2 {
				spTo := strings.Split(fkey.ToTableField, ".")
				if len(spTo) == 2 {
					q := "select * from " + spTo[0] + " order by " + spTo[1]
					mm := []map[string]any{}
					err := To(&mm).Query(q)
					if !lg.CheckError(err) {
						ress := []any{}
						for _, res := range mm {
							ress = append(ress, res[spTo[1]])
						}
						if len(ress) > 0 {
							mmfkeys[spFrom[1]] = ress
							mmfkeysModels[spFrom[1]] = mm
							for _, v := range mmfkeysModels[spFrom[1]] {
								for i, vv := range v {
									if vvStr, ok := vv.(string); ok {
										if len(vvStr) > TruncatePer {
											v[i] = vvStr[:TruncatePer] + "..."
										}
									}
								}
							}
						}
					} else {
						lg.ErrorC("error:", "q", q, "spTo", spTo)
					}
				}
			}
		}
	} else {
		idString = cols[0]
	}

	if dbMem != nil {
		ccc := cols
		if t != nil {
			ccc = t.Columns
		}

		data := map[string]any{
			"dbType":         dbMem.Dialect,
			"table":          model,
			"rows":           rows,
			"total":          total,
			"dbcolumns":      dbCols,
			"pk":             idString,
			"fkeys":          mmfkeys,
			"fkeysModels":    mmfkeysModels,
			"columnsOrdered": ccc,
		}
		if t != nil {
			data["columns"] = t.ModelTypes
		} else {
			data["columns"] = dbCols
		}
		c.Json(map[string]any{
			"success": data,
		})
	} else {
		lg.ErrorC("table not found", "table", model)
		c.Status(404).Json(map[string]any{
			"error": "table not found",
		})
	}
}
View Source
var TablesView = func(c *ksmux.Context) {
	allTables := GetAllTables(defaultDB)
	q := []string{}
	for _, t := range allTables {
		q = append(q, "SELECT '"+t+"' AS table_name,COUNT(*) AS count FROM "+t)
	}
	query := strings.Join(q, ` UNION ALL `)

	var results []struct {
		TableName string `db:"table_name"`
		Count     int    `db:"count"`
	}
	if err := To(&results).Query(query); lg.CheckError(err) {
		c.Error("something wrong happened")
		return
	}

	c.Html("admin/admin_tables.html", map[string]any{
		"tables":  allTables,
		"results": results,
	})
}
View Source
var TerminalComplete = func(c *ksmux.Context) {
	input := c.Request.URL.Query().Get("input")
	session := c.Request.URL.Query().Get("session")

	currentDir, _ := termsessions.Get(session)
	if currentDir == "" {
		currentDir, _ = os.Getwd()
	}

	parts := strings.Fields(input)
	if len(parts) == 0 {
		c.Json(map[string]any{"suggestions": []string{}})
		return
	}

	lastWord := parts[len(parts)-1]
	targetDir := currentDir

	if strings.Contains(lastWord, "/") {

		pathParts := strings.Split(lastWord, "/")

		searchPattern := pathParts[len(pathParts)-1]

		searchDir := strings.Join(pathParts[:len(pathParts)-1], "/")

		if filepath.IsAbs(searchDir) {
			targetDir = searchDir
		} else {
			targetDir = filepath.Join(currentDir, searchDir)
		}

		files, err := os.ReadDir(targetDir)
		if err != nil {
			lg.Error("Error reading directory:", err)
			c.Json(map[string]any{"suggestions": []string{}})
			return
		}

		suggestions := []string{}
		for _, file := range files {
			name := file.Name()
			if strings.HasPrefix(strings.ToLower(name), strings.ToLower(searchPattern)) {
				if file.IsDir() {
					name += "/"
				}

				suggestion := strings.Join([]string{searchDir, name}, "/")
				suggestions = append(suggestions, suggestion)
			}
		}

		c.Json(map[string]any{"suggestions": suggestions})
		return
	}

	files, err := os.ReadDir(targetDir)
	if err != nil {
		lg.Error("Error reading directory:", err)
		c.Json(map[string]any{"suggestions": []string{}})
		return
	}

	suggestions := []string{}
	for _, file := range files {
		name := file.Name()
		if strings.HasPrefix(strings.ToLower(name), strings.ToLower(lastWord)) {
			if file.IsDir() {
				name += "/"
			}
			suggestions = append(suggestions, name)
		}
	}

	c.Json(map[string]any{"suggestions": suggestions})
}
View Source
var TerminalExecute = func(c *ksmux.Context) {
	var req struct {
		Command string `json:"command"`
		Session string `json:"session"`
	}
	if err := c.BodyStruct(&req); err != nil {
		c.Json(map[string]any{"type": "error", "content": err.Error()})
		return
	}

	currentDir, _ := termsessions.Get(req.Session)
	if currentDir == "" {
		currentDir, _ = os.Getwd()
	}

	output, newDir := executeCommand(req.Command, currentDir)

	termsessions.Set(req.Session, newDir)
	lg.Debug("Updated session directory:", newDir)

	c.Json(map[string]any{
		"type":      "output",
		"content":   output,
		"directory": newDir,
	})
}

WebSocket endpoint for terminal

View Source
var TerminalGetView = func(c *ksmux.Context) {
	c.Html("admin/admin_terminal.html", nil)
}
View Source
var TracingGetView = func(c *ksmux.Context) {
	c.Html("admin/admin_tracing.html", nil)
}
View Source
var TruncatePer = 50
View Source
var UpdateRowPost = func(c *ksmux.Context) {

	data, files := c.ParseMultipartForm()
	id := data["row_id"][0]
	idString := "id"
	db, _ := GetMemoryDatabase(defaultDB)
	var t TableEntity
	for _, tab := range db.Tables {
		if tab.Name == data["table"][0] {
			t = tab
		}
	}
	if t.Pk != "" && t.Pk != "id" {
		idString = t.Pk
	}
	_, _, err := handleFilesUpload(files, data["table"][0], id, c, idString)
	if err != nil {
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": err.Error(),
		})
		return
	}

	modelDB, err := Table(data["table"][0]).Database(defaultDB).Where(idString+" = ?", id).One()

	if err != nil {
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": err.Error(),
		})
		return
	}

	ignored := []string{idString, "file", "image", "photo", "img", "fichier", "row_id", "table"}
	toUpdate := map[string]any{}
	quote := "`"
	if db.Dialect == POSTGRES || db.Dialect == COCKROACH {
		quote = "\""
	}
	for key, val := range data {
		if !SliceContains(ignored, key) {
			if modelDB[key] == val[0] {

				continue
			}
			if key == "password" || key == "pass" {
				hash, err := argon.Hash(val[0])
				if err != nil {
					c.Error("unable to hash pass")
					return
				}
				toUpdate[quote+key+quote] = hash
			} else {
				toUpdate[quote+key+quote] = val[0]
			}
		}
	}

	s := ""
	values := []any{}
	if len(toUpdate) > 0 {
		for col, v := range toUpdate {
			if s == "" {
				s += col + "= ?"
			} else {
				s += "," + col + "= ?"
			}
			values = append(values, v)
		}
	}
	if s != "" {
		_, err := Table(data["table"][0]).Database(defaultDB).Where(idString+" = ?", id).Set(s, values...)
		if err != nil {
			c.Status(http.StatusBadRequest).Json(map[string]any{
				"error": err.Error(),
			})
			return
		}
	}
	s = ""
	if len(files) > 0 {
		for f := range files {
			if s == "" {
				s += f
			} else {
				s += "," + f
			}
		}
	}
	if len(toUpdate) > 0 {
		for k := range toUpdate {
			if s == "" {
				s += k
			} else {
				s += "," + k
			}
		}
	}

	ret, err := Table(data["table"][0]).Database(defaultDB).Where(idString+" = ?", id).One()
	if err != nil {
		c.Status(500).Error("something wrong happened")
		return
	}

	c.Json(map[string]any{
		"success": ret,
	})
}

Functions ¶

func AdaptNamedParams ¶ added in v1.8.0

func AdaptNamedParams(dialect, statement string, variables map[string]any, unsafe ...bool) (string, []any, error)

func AdaptPlaceholdersToDialect ¶ added in v1.94.0

func AdaptPlaceholdersToDialect(query *string, dialect string)

func AddChangesTrigger ¶ added in v1.95.8

func AddChangesTrigger(tableName string, dbName ...string) error

AddChangesTrigger

func AddDashStats ¶ added in v1.95.2

func AddDashStats(fn ...StatsFunc)

func AddTrigger ¶

func AddTrigger(onTable, col, bf_af_UpdateInsertDelete string, stmt string, dbName ...string)

AddTrigger add trigger tablename_trig if col empty and tablename_trig_col if not

func AllowTerminalCommands ¶ added in v1.95.7

func AllowTerminalCommands(commands ...string)

AllowTerminalCommands adds commands to the allowed list for admin terminal

func AutoMigrate ¶

func AutoMigrate[T any](tableName string, dbName ...string) error

func Benchmark ¶ added in v1.2.5

func Benchmark(f func(), name string, iterations int)

Benchmark benchmark a function

func ClearDBTraces ¶ added in v1.95.0

func ClearDBTraces()

ClearDBTraces removes all stored traces

func DebugNodeManager ¶ added in v1.95.9

func DebugNodeManager()

func DifferenceBetweenSlices ¶ added in v1.4.1

func DifferenceBetweenSlices[T comparable](slice1 []T, slice2 []T) []T

func DisableCache ¶

func DisableCache()

DisableCache disable the cache system, if and only if you are having problem with it, also you can korm.FlushCache on command too

func DisableTracing ¶ added in v1.95.0

func DisableTracing()

DisableTracing turns off query tracing

func DisallowTerminalCommands ¶ added in v1.95.7

func DisallowTerminalCommands(commands ...string)

DisallowTerminalCommands removes commands from the allowed list

func DownloadFile ¶ added in v1.6.1

func DownloadFile(filepath string, url string) error

func DropTrigger ¶

func DropTrigger(tableName, column string, dbName ...string)

DropTrigger drop trigger tablename_trig if column empty and tablename_trig_column if not

func Exec ¶ added in v1.1.0

func Exec(dbName, query string, args ...any) error

Exec exec sql and return error if any

func ExecContext ¶ added in v1.8.0

func ExecContext(ctx context.Context, dbName, query string, args ...any) error

ExecContext exec sql and return error if any

func ExecContextNamed ¶ added in v1.8.0

func ExecContextNamed(ctx context.Context, query string, args map[string]any, dbName ...string) error

ExecContextNamed exec named sql and return error if any

func ExecNamed ¶ added in v1.8.0

func ExecNamed(query string, args map[string]any, dbName ...string) error

ExecNamed exec named sql and return error if any

func FlushCache ¶

func FlushCache(tables ...string)

FlushCache send msg to the cache system to Flush all the cache, safe to use in concurrent mode, and safe to use in general, flushed every (korm.FlushCacheEvery)

func GenerateUUID ¶

func GenerateUUID() string

func GetAllColumnsTypes ¶

func GetAllColumnsTypes(table string, dbName ...string) (map[string]string, []string)

GetAllColumnsTypes get columns and types from the database

func GetAllTables ¶

func GetAllTables(dbName ...string) []string

GetAllTables get all tables for the optional dbName given, otherwise, if not args, it will return tables of the first connected database

func GetConnection ¶ added in v0.2.0

func GetConnection(dbName ...string) *sql.DB

GetConnection get connection of dbName, if not specified , it return default, first database connected

func GetDatabaseSize ¶ added in v1.94.4

func GetDatabaseSize(dbName string) (string, error)

GetDatabaseSize returns the size of the database in GB or MB

func GetMemoryTableAndDB ¶ added in v1.4.1

func GetMemoryTableAndDB(tbName string, dbName ...string) (TableEntity, DatabaseEntity, error)

GetMemoryTable get a table from memory for specified or first connected db

func GetTotalRequests ¶ added in v1.94.4

func GetTotalRequests() uint64

GetTotalRequests returns the current total requests count

func In ¶ added in v1.94.0

func In(query string, args ...any) (string, []any)

In expands slice arguments for IN clauses before dialect adaptation

func InitShell ¶

func InitShell() bool

InitShell init the shell and return true if used to stop main

func IsValidEmail ¶ added in v1.5.4

func IsValidEmail(email string) bool

func JSON_ARRAY ¶ added in v1.93.2

func JSON_ARRAY(values []any, as string, dialect ...string) string

func JSON_CAST ¶ added in v1.93.2

func JSON_CAST(value string, as string, dialect ...string) string

func JSON_EXTRACT ¶ added in v1.93.2

func JSON_EXTRACT(dataJson string, opt ...JsonOption) string

func JSON_OBJECT ¶ added in v1.93.2

func JSON_OBJECT(values []any, as string, dialect ...string) string

func JSON_REMOVE ¶ added in v1.93.2

func JSON_REMOVE(dataJson string, opt ...JsonOption) string

func JSON_SET ¶ added in v1.93.2

func JSON_SET(dataJson string, opt ...JsonOption) string

func LinkModel ¶

func LinkModel[T any](to_table_name string, dbName ...string)

LinkModel link a struct model to a db_table_name

func LogQueries ¶ added in v1.7.7

func LogQueries()

LogQueries enable logging sql statements with time tooked

func ManyToMany ¶ added in v1.1.0

func ManyToMany(table1, table2 string, dbName ...string) error

ManyToMany create m2m_table1_table2 many 2 many table

func New ¶ added in v0.4.0

func New(dbType Dialect, dbName string, dbDriver driver.Driver, dbDSN ...string) error

New the generic way to connect to all handled databases

Example:
  korm.New(korm.SQLITE, "db", sqlitedriver.Use())
  korm.New(korm.MYSQL,"dbName", mysqldriver.Use(), "user:password@localhost:3333")
  korm.New(korm.POSTGRES,"dbName", pgdriver.Use(), "user:password@localhost:5432")

func OnDelete ¶ added in v1.3.4

func OnDelete(fn HookFunc)

func OnDrop ¶ added in v1.3.4

func OnDrop(fn HookFunc)

func OnInsert ¶ added in v1.3.4

func OnInsert(fn HookFunc)

func OnSet ¶ added in v1.3.4

func OnSet(fn HookFunc)

func PrintSystemMetrics ¶ added in v1.95.9

func PrintSystemMetrics()

PrintSystemMetrics prints the current system metrics

func RemoveFromSlice ¶ added in v1.4.1

func RemoveFromSlice[T comparable](slice *[]T, elemsToRemove ...T)

func ResetStruct ¶ added in v1.93.6

func ResetStruct(input interface{}) error

The input can be a struct, a pointer to a struct, or a pointer to a pointer to a struct.

func RunEvery ¶

func RunEvery(t time.Duration, fn func(cancelChan chan struct{}))

func SetAdminPath ¶ added in v1.91.92

func SetAdminPath(path string)

SetAdminPath set admin path, default '/admin'

func SetCacheMaxMemory ¶ added in v1.4.3

func SetCacheMaxMemory(megaByte int)

SetCacheMaxMemory set max size of each cache cacheAllS AllM, minimum of 50 ...

func SetMaxDBTraces ¶ added in v1.95.0

func SetMaxDBTraces(max int)

SetMaxDBTraces sets the maximum number of traces to keep

func Shutdown ¶ added in v1.1.0

func Shutdown(dbNames ...string) error

Shutdown shutdown many database

func SliceContains ¶

func SliceContains[T comparable](elems []T, vs ...T) bool

func SliceToString ¶ added in v1.93.2

func SliceToString(slice interface{}) string

func StorageSize ¶ added in v1.4.5

func StorageSize(dbName string) float64

func Transaction ¶ added in v1.3.9

func Transaction(dbName ...string) (*sql.Tx, error)

Transaction create new database/sql transaction and return it, it can be rollback ...

func UnsafeNamedQuery ¶ added in v1.8.1

func UnsafeNamedQuery(query string, args map[string]any) (string, error)

func WithBus ¶ added in v0.4.0

func WithBus(config ...ksmux.Config) *kactor.BusServer

WithBus return ksbus.NewServer() that can be Run, RunTLS, RunAutoTLS

func WithDashboard ¶ added in v1.3.0

func WithDashboard(addr string, options ...DashOpts) *kactor.BusServer

WithDashboard enable admin dashboard

func WithDocs ¶ added in v1.6.1

func WithDocs(generateJsonDocs bool, outJsonDocs string, handlerMiddlewares ...func(handler ksmux.Handler) ksmux.Handler) *kactor.BusServer

WithDocs enable swagger docs at DocsUrl default to '/docs/'

func WithEmbededDocs ¶ added in v1.6.4

func WithEmbededDocs(embeded embed.FS, embededDirPath string, handlerMiddlewares ...func(handler ksmux.Handler) ksmux.Handler) *kactor.BusServer

WithEmbededDocs same as WithDocs but embeded, enable swagger docs at DocsUrl default to '/docs/'

func WithMetrics ¶ added in v1.5.9

func WithMetrics(httpHandler http.Handler) *kactor.BusServer

WithMetrics enable path /metrics (default), it take http.Handler like promhttp.Handler()

func WithPprof ¶ added in v1.5.9

func WithPprof(path ...string) *kactor.BusServer

WithPprof enable std library pprof at /debug/pprof, prefix default to 'debug'

func WithSchemaCheck ¶ added in v1.95.9

func WithSchemaCheck()

WithSchemaCheck enable struct changes check

func WithShell ¶ added in v1.5.3

func WithShell()

WithShell enable shell, go run main.go shell

func WithTracing ¶ added in v1.95.0

func WithTracing()

WithTracing turns on tracing db + api

func Wrap ¶ added in v1.7.7

func Wrap(driver driver.Driver, hooks Hooks) driver.Driver

func WrapConn ¶ added in v1.8.0

func WrapConn(conn driver.Conn, hooks Hooks) driver.Conn

Types ¶

type BuilderM ¶

type BuilderM struct {
	// contains filtered or unexported fields
}

BuilderM is query builder map string any

func BuilderMap ¶

func BuilderMap() *BuilderM

func Table ¶

func Table(tableName string) *BuilderM

Table is a starter for BuiderM

func (*BuilderM) AddRelated ¶ added in v1.1.0

func (b *BuilderM) AddRelated(relatedTable string, whereRelatedTable string, whereRelatedArgs ...any) (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 them

func (*BuilderM) All ¶

func (b *BuilderM) All() ([]map[string]any, error)

All get all data

func (*BuilderM) BulkInsert ¶ added in v1.2.6

func (b *BuilderM) BulkInsert(rowsData ...map[string]any) ([]int, error)

BulkInsert insert many row at the same time in one query

func (*BuilderM) Context ¶

func (b *BuilderM) Context(ctx context.Context) *BuilderM

Context allow to query or execute using ctx

func (*BuilderM) Database ¶

func (b *BuilderM) Database(dbName string) *BuilderM

Database allow to choose database to execute query on

func (*BuilderM) Debug ¶

func (b *BuilderM) Debug() *BuilderM

Debug print prepared statement and values for this operation

func (*BuilderM) Delete ¶

func (b *BuilderM) Delete() (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 (*BuilderM) DeleteRelated ¶ added in v1.1.0

func (b *BuilderM) DeleteRelated(relatedTable string, whereRelatedTable string, whereRelatedArgs ...any) (int, error)

DeleteRelated delete a relations many to many

func (*BuilderM) Drop ¶

func (b *BuilderM) Drop() (int, error)

Drop drop table from db

func (*BuilderM) GetRelated ¶ added in v1.1.0

func (b *BuilderM) GetRelated(relatedTable string, dest *[]map[string]any) error

GetRelated used for many to many to get related classes to a student or related students to a class

func (*BuilderM) Insert ¶

func (b *BuilderM) Insert(rowData map[string]any) (int, error)

Insert add row to a table using input map, and return PK of the inserted row

func (*BuilderM) InsertR ¶ added in v1.3.6

func (b *BuilderM) InsertR(rowData map[string]any) (map[string]any, error)

InsertR add row to a table using input map, and return the inserted row

func (*BuilderM) JoinRelated ¶ added in v1.1.0

func (b *BuilderM) JoinRelated(relatedTable string, dest *[]map[string]any) error

JoinRelated same as get, but it join data

func (*BuilderM) Limit ¶

func (b *BuilderM) Limit(limit int) *BuilderM

Limit set limit

func (*BuilderM) NoCache ¶ added in v1.91.95

func (b *BuilderM) NoCache() *BuilderM

func (*BuilderM) One ¶

func (b *BuilderM) One() (map[string]any, error)

One get single row

func (*BuilderM) OrderBy ¶

func (b *BuilderM) OrderBy(fields ...string) *BuilderM

OrderBy can be used like: OrderBy("-id","-email") OrderBy("id","-email") OrderBy("+id","email")

func (*BuilderM) Page ¶

func (b *BuilderM) Page(pageNumber int) *BuilderM

Page return paginated elements using Limit for specific page

func (*BuilderM) QueryM ¶ added in v1.9.5

func (b *BuilderM) QueryM(statement string, args ...any) ([]map[string]any, error)

QueryM query sql and return result as slice maps

func (*BuilderM) QueryMNamed ¶ added in v1.9.5

func (b *BuilderM) QueryMNamed(statement string, args map[string]any, unsafe ...bool) ([]map[string]any, error)

QueryMNamed query sql and return result as slice maps

Example:

	QueryMNamed("select * from users where email = :email",map[string]any{
		"email":"email@mail.com",
    })

func (*BuilderM) Select ¶

func (b *BuilderM) Select(columns ...string) *BuilderM

Select select table columns to return

func (*BuilderM) Set ¶

func (b *BuilderM) Set(query string, args ...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 (*BuilderM) SetM ¶ added in v1.95.8

func (b *BuilderM) SetM(data map[string]any) (int, error)

func (*BuilderM) Trace ¶ added in v1.95.0

func (b *BuilderM) Trace() *BuilderM

func (*BuilderM) Where ¶

func (b *BuilderM) Where(query string, args ...any) *BuilderM

Where can be like: Where("id > ?",1) or Where("id = ?",1)

func (*BuilderM) WhereNamed ¶ added in v1.8.0

func (b *BuilderM) WhereNamed(query string, args map[string]any) *BuilderM

WhereNamed can be like : Where("email = :email",map[string]any{"email":"abc@mail.com"})

type BuilderS ¶

type BuilderS[T any] struct {
	// contains filtered or unexported fields
}

BuilderS is query builder for struct using generics

func BuilderStruct ¶ added in v1.0.2

func BuilderStruct[T any](model ...T) *BuilderS[T]

BuilderStruct empty query to struct starter, default db first connected

func Model ¶

func Model[T any](model ...T) *BuilderS[T]

Model is a starter for Buider

func ModelTable ¶ added in v1.4.9

func ModelTable[T any](tableName string, model ...T) *BuilderS[T]

func (*BuilderS[T]) AddRelated ¶ added in v1.3.6

func (b *BuilderS[T]) AddRelated(relatedTable string, whereRelatedTable string, whereRelatedArgs ...any) (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 them

func (*BuilderS[T]) All ¶ added in v1.3.6

func (b *BuilderS[T]) All() ([]T, error)

All get all data

func (*BuilderS[T]) BulkInsert ¶ added in v1.3.6

func (b *BuilderS[T]) BulkInsert(models ...*T) ([]int, error)

BulkInsert insert many row at the same time in one query

func (*BuilderS[T]) Context ¶ added in v1.3.6

func (b *BuilderS[T]) Context(ctx context.Context) *BuilderS[T]

Context allow to query or execute using ctx

func (*BuilderS[T]) Database ¶ added in v1.3.6

func (b *BuilderS[T]) Database(dbName string) *BuilderS[T]

Database allow to choose database to execute query on

func (*BuilderS[T]) Debug ¶ added in v1.3.6

func (b *BuilderS[T]) Debug() *BuilderS[T]

Debug print prepared statement and values for this operation

func (*BuilderS[T]) Delete ¶ added in v1.3.6

func (b *BuilderS[T]) Delete() (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 (*BuilderS[T]) DeleteRelated ¶ added in v1.3.6

func (b *BuilderS[T]) DeleteRelated(relatedTable string, whereRelatedTable string, whereRelatedArgs ...any) (int, error)

DeleteRelated delete a relations many to many

func (*BuilderS[T]) Drop ¶ added in v1.3.6

func (b *BuilderS[T]) Drop() (int, error)

Drop drop table from db

func (*BuilderS[T]) GetRelated ¶ added in v1.3.6

func (b *BuilderS[T]) GetRelated(relatedTable string, dest any) error

GetRelated used for many to many to get related classes to a student or related students to a class

func (*BuilderS[T]) Insert ¶ added in v1.3.6

func (b *BuilderS[T]) Insert(model *T) (int, error)

func (*BuilderS[T]) InsertR ¶ added in v1.3.6

func (b *BuilderS[T]) InsertR(model *T) (T, error)

InsertR add row to a table using input struct, and return the inserted row

func (*BuilderS[T]) JoinRelated ¶ added in v1.3.6

func (b *BuilderS[T]) JoinRelated(relatedTable string, dest any) error

JoinRelated same as get, but it join data

func (*BuilderS[T]) Limit ¶ added in v1.3.6

func (b *BuilderS[T]) Limit(limit int) *BuilderS[T]

Limit set limit

func (*BuilderS[T]) NoCache ¶ added in v1.91.95

func (b *BuilderS[T]) NoCache() *BuilderS[T]

func (*BuilderS[T]) One ¶ added in v1.3.6

func (b *BuilderS[T]) One() (T, error)

One get single row

func (*BuilderS[T]) OrderBy ¶ added in v1.3.6

func (b *BuilderS[T]) OrderBy(fields ...string) *BuilderS[T]

OrderBy can be used like: OrderBy("-id","-email") OrderBy("id","-email") OrderBy("+id","email")

func (*BuilderS[T]) Page ¶ added in v1.3.6

func (b *BuilderS[T]) Page(pageNumber int) *BuilderS[T]

Page return paginated elements using Limit for specific page

func (*BuilderS[T]) QueryS ¶ added in v1.9.5

func (b *BuilderS[T]) QueryS(statement string, args ...any) ([]T, error)

QueryS query to struct

func (*BuilderS[T]) QuerySNamed ¶ added in v1.9.5

func (b *BuilderS[T]) QuerySNamed(statement string, args map[string]any, unsafe ...bool) ([]T, error)

QueryNamedS query sql and return result as slice of structs T

Example:

	QuerySNamed[models.User]("select * from users where email = :email",map[string]any{
		"email":"email@mail.com",
    })

func (*BuilderS[T]) Select ¶ added in v1.3.6

func (b *BuilderS[T]) Select(columns ...string) *BuilderS[T]

Select usage: Select("email","password")

func (*BuilderS[T]) Set ¶ added in v1.3.6

func (b *BuilderS[T]) Set(query string, args ...any) (int, error)

Set used to update, Set("email,is_admin","example@mail.com",true) or Set("email = ? , is_admin = ?","example@mail.com",true)

func (*BuilderS[T]) SetM ¶ added in v1.95.8

func (b *BuilderS[T]) SetM(data map[string]any) (int, error)

func (*BuilderS[T]) ToChan ¶ added in v1.8.5

func (b *BuilderS[T]) ToChan(ptrChan *chan T) ([]T, error)

func (*BuilderS[T]) Trace ¶ added in v1.95.0

func (b *BuilderS[T]) Trace() *BuilderS[T]

func (*BuilderS[T]) Where ¶ added in v1.3.6

func (b *BuilderS[T]) Where(query string, args ...any) *BuilderS[T]

Where can be like : Where("id > ? AND age IN (?)",[]uint{18,19,20}) or Where("id",1) = Where("id = ?",1)

func (*BuilderS[T]) WhereNamed ¶ added in v1.8.0

func (b *BuilderS[T]) WhereNamed(query string, args map[string]any) *BuilderS[T]

WhereNamed can be like : Where("email = :email",map[string]any{"email":"abc@mail.com"})

type DashOpts ¶ added in v1.92.8

type DashOpts struct {
	ServerOpts         *ksmux.Config
	EmbededStatic      embed.FS
	EmbededTemplates   embed.FS
	PaginatePer        int    // default 10
	DocsUrl            string // default docs
	MediaDir           string // default media
	BaseDir            string // default assets
	StaticDir          string // default BaseDir/static
	TemplatesDir       string // default BaseDir/templates
	Path               string // default /admin
	RepoUser           string // default kamalshkeir
	RepoName           string // default korm-dash
	WithTracing        bool   // add tracing handling page in dash and enable tracing
	WithTerminal       bool   // add terminal session handling page in dash
	WithNodeManager    bool   // add node manager handling page in dash
	WithRequestCounter bool   // add request counter dashboard,default false
}

type DatabaseEntity ¶

type DatabaseEntity struct {
	Tables  []TableEntity
	Name    string
	Dialect string
	Conn    *sql.DB
}

DatabaseEntity hold memory db state

func GetDefaultDbMem ¶ added in v1.95.9

func GetDefaultDbMem() *DatabaseEntity

func GetMemoryDatabase ¶

func GetMemoryDatabase(dbName string) (*DatabaseEntity, error)

GetMemoryDatabase return the first connected database korm.DefaultDatabase if dbName "" or "default" else the matched db

func GetMemoryDatabases ¶

func GetMemoryDatabases() []DatabaseEntity

GetMemoryDatabases get all databases from memory

type Dialect ¶ added in v1.1.0

type Dialect = string

Dialect db dialects are SQLITE, POSTGRES, MYSQL, MARIA, COCKROACH

const (
	MIGRATION_FOLDER         = "migrations"
	SQLITE           Dialect = "sqlite3"
	POSTGRES         Dialect = "postgres"
	MYSQL            Dialect = "mysql"
	MARIA            Dialect = "maria"
	COCKROACH        Dialect = "cockroach"
)

type DocsError ¶ added in v1.6.9

type DocsError struct {
	Error string `json:"error" example:"error message"`
}

type DocsSuccess ¶ added in v1.6.9

type DocsSuccess struct {
	Success string `json:"success" example:"success message"`
}

type Driver ¶ added in v1.7.7

type Driver struct {
	driver.Driver
	// contains filtered or unexported fields
}

Driver implements a database/sql/driver.Driver

func (*Driver) Open ¶ added in v1.7.7

func (drv *Driver) Open(name string) (driver.Conn, error)

Open opens a connection

type ExecerQueryerContext ¶ added in v1.7.7

type ExecerQueryerContext struct {
	// contains filtered or unexported fields
}

ExecerQueryerContext implements database/sql.driver.ExecerContext and database/sql.driver.QueryerContext

func (ExecerQueryerContext) Begin ¶ added in v1.8.0

func (conn ExecerQueryerContext) Begin() (driver.Tx, error)

func (ExecerQueryerContext) BeginTx ¶ added in v1.8.0

func (conn ExecerQueryerContext) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error)

func (ExecerQueryerContext) Close ¶ added in v1.8.0

func (conn ExecerQueryerContext) Close() error

func (ExecerQueryerContext) Exec ¶ added in v1.8.0

func (conn ExecerQueryerContext) Exec(query string, args []driver.Value) (driver.Result, error)

func (ExecerQueryerContext) ExecContext ¶ added in v1.8.0

func (conn ExecerQueryerContext) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error)

func (ExecerQueryerContext) Prepare ¶ added in v1.8.0

func (conn ExecerQueryerContext) Prepare(query string) (driver.Stmt, error)

func (ExecerQueryerContext) PrepareContext ¶ added in v1.8.0

func (conn ExecerQueryerContext) PrepareContext(ctx context.Context, query string) (driver.Stmt, error)

func (ExecerQueryerContext) QueryContext ¶ added in v1.8.0

func (conn ExecerQueryerContext) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error)

type ExecerQueryerContextWithSessionResetter ¶ added in v1.7.7

type ExecerQueryerContextWithSessionResetter struct {
	*SessionResetter
	// contains filtered or unexported fields
}

ExecerQueryerContext implements database/sql.driver.ExecerContext and database/sql.driver.QueryerContext

func (ExecerQueryerContextWithSessionResetter) Begin ¶ added in v1.8.0

func (conn ExecerQueryerContextWithSessionResetter) Begin() (driver.Tx, error)

func (ExecerQueryerContextWithSessionResetter) BeginTx ¶ added in v1.8.0

func (conn ExecerQueryerContextWithSessionResetter) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error)

func (ExecerQueryerContextWithSessionResetter) Close ¶ added in v1.8.0

func (conn ExecerQueryerContextWithSessionResetter) Close() error

func (ExecerQueryerContextWithSessionResetter) Exec ¶ added in v1.8.0

func (conn ExecerQueryerContextWithSessionResetter) Exec(query string, args []driver.Value) (driver.Result, error)

func (ExecerQueryerContextWithSessionResetter) ExecContext ¶ added in v1.8.0

func (conn ExecerQueryerContextWithSessionResetter) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error)

func (ExecerQueryerContextWithSessionResetter) Prepare ¶ added in v1.8.0

func (conn ExecerQueryerContextWithSessionResetter) Prepare(query string) (driver.Stmt, error)

func (ExecerQueryerContextWithSessionResetter) PrepareContext ¶ added in v1.8.0

func (conn ExecerQueryerContextWithSessionResetter) PrepareContext(ctx context.Context, query string) (driver.Stmt, error)

func (ExecerQueryerContextWithSessionResetter) QueryContext ¶ added in v1.8.0

func (conn ExecerQueryerContextWithSessionResetter) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error)

type HookData ¶ added in v1.95.8

type HookData struct {
	Pk        string         `json:"pk"`
	Table     string         `json:"table"`
	Operation string         `json:"operation"`
	Data      map[string]any `json:"data"`
	Old       map[string]any `json:"old"`
	New       map[string]any `json:"new"`
}

type HookFunc ¶ added in v1.95.8

type HookFunc func(HookData)

type Hooks ¶ added in v1.7.7

type Hooks interface {
	Before(ctx context.Context, query string, args ...interface{}) (context.Context, error)
	After(ctx context.Context, query string, args ...interface{}) (context.Context, error)
}

Hooks instances may be passed to Wrap() to define an instrumented driver

type JsonOption ¶ added in v1.93.2

type JsonOption struct {
	As       string
	Dialect  string
	Database string
	Params   []any
}

type KV ¶ added in v1.9.6

type KV kstrct.KV

type LogEntry ¶ added in v1.94.4

type LogEntry struct {
	Type  string
	At    string
	Extra string
}

type Node ¶ added in v1.95.9

type Node struct {
	ID      string `json:"id"`
	Address string `json:"address"`
	Active  bool   `json:"active"`
	Secure  bool   `json:"secure"`
}

Node represents a KORM node in the cluster

type NodeManager ¶ added in v1.95.9

type NodeManager struct {
	// contains filtered or unexported fields
}

NodeManager handles node registration and data synchronization

func GetNodeManager ¶ added in v1.95.9

func GetNodeManager() *NodeManager

func WithNodeManager ¶ added in v1.95.9

func WithNodeManager() *NodeManager

func (*NodeManager) AddNode ¶ added in v1.95.9

func (nm *NodeManager) AddNode(node *Node) error

func (*NodeManager) GetNode ¶ added in v1.95.9

func (nm *NodeManager) GetNode(addr string) *Node

func (*NodeManager) GetNodes ¶ added in v1.95.9

func (nm *NodeManager) GetNodes() []*Node

func (*NodeManager) IsSecure ¶ added in v1.95.9

func (nm *NodeManager) IsSecure(addr string) bool

func (*NodeManager) RemoveNode ¶ added in v1.95.9

func (nm *NodeManager) RemoveNode(nodeAddr string)

func (*NodeManager) Shutdown ¶ added in v1.95.9

func (nm *NodeManager) Shutdown()

func (*NodeManager) SyncData ¶ added in v1.95.9

func (nm *NodeManager) SyncData(targetNode *Node) error

SyncData send all tables to targetNode sync_data

type OnErrorer ¶ added in v1.7.7

type OnErrorer interface {
	OnError(ctx context.Context, err error, query string, args ...interface{}) error
}

OnErrorer instances will be called if any error happens

type Selector ¶ added in v1.9.6

type Selector[T any] struct {
	// contains filtered or unexported fields
}

func To ¶ added in v1.9.6

func To[T any](dest *[]T, nestedSlice ...bool) *Selector[T]

func (*Selector[T]) Ctx ¶ added in v1.9.6

func (sl *Selector[T]) Ctx(ct context.Context) *Selector[T]

func (*Selector[T]) Database ¶ added in v1.91.10

func (sl *Selector[T]) Database(dbName string) *Selector[T]

func (*Selector[T]) Debug ¶ added in v1.91.10

func (sl *Selector[T]) Debug() *Selector[T]

func (*Selector[T]) Named ¶ added in v1.9.6

func (sl *Selector[T]) Named(statement string, args map[string]any, unsafe ...bool) error

func (*Selector[T]) NoCache ¶ added in v1.91.95

func (sl *Selector[T]) NoCache() *Selector[T]

func (*Selector[T]) Query ¶ added in v1.9.6

func (sl *Selector[T]) Query(statement string, args ...any) error

func (*Selector[T]) Trace ¶ added in v1.95.0

func (sl *Selector[T]) Trace() *Selector[T]

type SessionResetter ¶ added in v1.7.7

type SessionResetter struct {
	// contains filtered or unexported fields
}

func (SessionResetter) Begin ¶ added in v1.8.0

func (conn SessionResetter) Begin() (driver.Tx, error)

func (SessionResetter) BeginTx ¶ added in v1.8.0

func (conn SessionResetter) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error)

func (SessionResetter) Close ¶ added in v1.8.0

func (conn SessionResetter) Close() error

func (SessionResetter) Prepare ¶ added in v1.8.0

func (conn SessionResetter) Prepare(query string) (driver.Stmt, error)

func (SessionResetter) PrepareContext ¶ added in v1.8.0

func (conn SessionResetter) PrepareContext(ctx context.Context, query string) (driver.Stmt, error)

type StatsFunc ¶ added in v1.95.2

type StatsFunc struct {
	Name string
	Func func() string
}

func GetStats ¶ added in v1.95.2

func GetStats() []StatsFunc

type Stmt ¶ added in v1.7.7

type Stmt struct {
	Stmt driver.Stmt
	// contains filtered or unexported fields
}

Stmt implements a database/sql/driver.Stmt

func (*Stmt) Close ¶ added in v1.7.7

func (stmt *Stmt) Close() error

func (*Stmt) Exec ¶ added in v1.7.7

func (stmt *Stmt) Exec(args []driver.Value) (driver.Result, error)

func (*Stmt) ExecContext ¶ added in v1.7.7

func (stmt *Stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error)

func (*Stmt) NumInput ¶ added in v1.7.7

func (stmt *Stmt) NumInput() int

func (*Stmt) Query ¶ added in v1.7.7

func (stmt *Stmt) Query(args []driver.Value) (driver.Rows, error)

func (*Stmt) QueryContext ¶ added in v1.7.7

func (stmt *Stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error)

type SystemMetrics ¶ added in v1.95.9

type SystemMetrics struct {
	// Memory metrics
	HeapMemoryMB   float64 // Currently allocated heap memory in MB
	SystemMemoryMB float64 // Total memory obtained from system in MB
	StackMemoryMB  float64 // Memory used by goroutine stacks
	HeapObjects    uint64  // Number of allocated heap objects
	HeapReleasedMB float64 // Memory released to the OS in MB

	// Garbage Collection metrics
	NumGC         uint32  // Number of completed GC cycles
	LastGCTimeSec float64 // Time since last garbage collection in seconds
	GCCPUPercent  float64 // Fraction of CPU time used by GC (0-100)

	// Runtime metrics
	NumGoroutines int    // Current number of goroutines
	NumCPU        int    // Number of logical CPUs
	GoVersion     string // Go version used to build the program
}

SystemMetrics holds memory and runtime statistics for the application

func GetSystemMetrics ¶ added in v1.95.9

func GetSystemMetrics() SystemMetrics

GetSystemMetrics returns memory and runtime statistics for the application

type TableEntity ¶

type TableEntity struct {
	Types      map[string]string
	ModelTypes map[string]string
	Tags       map[string][]string
	Columns    []string
	Fkeys      []kormFkey
	Pk         string
	Name       string
}

DatabaseEntity hold table state

func GetMemoryTable ¶

func GetMemoryTable(tbName string, dbName ...string) (TableEntity, error)

GetMemoryTable get a table from memory for specified or first connected db

func GetTablesInfosFromDB ¶ added in v1.95.9

func GetTablesInfosFromDB(tables ...string) []TableEntity

type TablesInfos ¶ added in v1.95.9

type TablesInfos struct {
	Id         uint
	Name       string
	Pk         string
	Types      map[string]string
	ModelTypes map[string]string
	Tags       map[string][]string
	Columns    []string
	Fkeys      []string
}

type TraceData ¶ added in v1.95.0

type TraceData struct {
	Query     string        // The SQL query
	Args      []any         // Query arguments
	Database  string        // Database name
	StartTime time.Time     // When the query started
	Duration  time.Duration // How long it took
	Error     error         // Any error that occurred
}

TraceData represents a single trace entry

func GetDBTraces ¶ added in v1.95.0

func GetDBTraces() []TraceData

GetDBTraces returns all stored traces

func TraceQuery ¶ added in v1.95.0

func TraceQuery(ctx context.Context, db *DatabaseEntity, query string, args ...any) (TraceData, error)

TraceQuery wraps a query execution with tracing

type Tracer ¶ added in v1.95.0

type Tracer struct {
	// contains filtered or unexported fields
}

Tracer handles query tracing functionality

type TriggersQueue ¶ added in v1.95.8

type TriggersQueue struct {
	Id   uint   `korm:"pk"`
	Data string `korm:"text"`
}

type User ¶ added in v1.3.0

type User struct {
	Id        int       `json:"id,omitempty" korm:"pk"`
	Uuid      string    `json:"uuid,omitempty" korm:"size:40;iunique"`
	Username  string    `json:"username,omitempty" korm:"size:40;iunique"`
	Email     string    `json:"email,omitempty" korm:"size:50;iunique"`
	Password  string    `json:"password,omitempty" korm:"size:150;default:''"`
	IsAdmin   bool      `json:"is_admin,omitempty" korm:"default:false"`
	Image     string    `json:"image,omitempty" korm:"size:100;default:''"`
	CreatedAt time.Time `json:"created_at,omitempty" korm:"now"`
}

Directories ¶

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL