korm

package module
v1.93.0 Latest Latest
Warning

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

Go to latest
Published: Apr 20, 2024 License: BSD-3-Clause Imports: 35 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 // v1.93.0

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-dashboard"
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 korm api

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())
	if lg.CheckError(err) {
		return
	}
	defer korm.Shutdown()

	serverBus := korm.WithDashboard("localhost:9313")
	korm.WithShell() // to enable shell
	app := bus.App
	lg.Printfs("mgrunning on http://localhost:9313\n")
	app.Get("/", func(c *ksmux.Context) {
		c.Text("hello")
	})

	korm.BASIC_AUTH_USER = "user"
	korm.BASIC_AUTH_PASS = "pass"
	err = korm.WithAPI("/api", korm.BasicAuth)
	if lg.CheckError(err) {
		return
	}
	// Register a table to the api

	// err = korm.RegisterTable(korm.TableRegistration[korm.User]{} // use defaults , all methods, Auth middleware, and no filters for query 
	// or , customize it : 
	err = korm.RegisterTable(korm.TableRegistration[korm.User]{
		//Methods: []string{"*"}, // default
		Methods: []string{"get","post"}, 

		//Middws: []func(handler kmux.Handler) kmux.Handler{korm.Auth}, // default
		Middws: []func(handler kmux.Handler) kmux.Handler{korm.Admin}, // users only accessible by admin

		BuilderGetAll: func(modelBuilder *korm.BuilderS[korm.User]) *korm.BuilderS[korm.User] { // for /users
			modelBuilder.Select("id")
			return modelBuilder
		},
		BuilderGetOne: func(modelBuilder *korm.BuilderS[korm.User]) *korm.BuilderS[korm.User] { // for /users/:pk
			modelBuilder.Select("uuid","email")
			return modelBuilder
		},
	})
	if lg.CheckError(err) {
		return
	}

	bus.Run()
}

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)  
*   now (NOT NULL and defaulted to current unix timestamp)
*   update (NOT NULL DEFAULT UNIX_TIMESTAMP ON UPDATE UNIX_TIMESTAMP)
*   fk:...
*   check:...

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:...

🔗 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.Error(404, "Unable to find this model")
				return
			}
		}
	}
	dbCols, cols := GetAllColumnsTypes(model)
	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 " + spTo[1] + " 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
						}
					} else {
						lg.ErrorC("error:", "q", q, "spTo", spTo)
					}
				}
			}
		}
	} else {
		idString = cols[0]
	}

	if dbMem != nil {
		data := map[string]any{
			"dbType":         dbMem.Dialect,
			"model_name":     model,
			"rows":           rows,
			"dbcolumns":      dbCols,
			"pk":             idString,
			"fkeys":          mmfkeys,
			"columnsOrdered": cols,
		}
		if t != nil {
			data["columns"] = t.ModelTypes
		} else {
			data["columns"] = dbCols
		}
		data["admin_path"] = adminPathNameGroup
		data["static_url"] = staticUrl
		c.Html("admin/admin_all_models.html", data)
	} else {
		lg.ErrorC("table not found", "table", model)
		c.Error(404, "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
	}
	if orderby, ok := body["orderby"]; ok {
		if v, ok := orderby.(string); ok && v != "" {
			oB = v
		} else {
			oB = "-" + t.Pk
		}
	} else {
		oB = "-" + t.Pk
	}
	blder.OrderBy(oB)
	if v, ok := body["page_num"]; ok && v != "" {
		if page, ok := v.(string); !ok {
			c.Status(http.StatusBadRequest).Json(map[string]any{
				"error": "expecting page_num to be a sring",
			})
			return
		} else {
			pagenum, err := strconv.Atoi(page)
			if err == nil {
				blder.Limit(paginationPer).Page(pagenum)
			} else {
				c.Status(http.StatusBadRequest).Json(map[string]any{
					"error": err.Error(),
				})
				return
			}
		}
	} else {
		blder.Limit(paginationPer).Page(1)
	}

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

	c.Json(map[string]any{
		"rows":  data,
		"cols":  t.Columns,
		"types": t.ModelTypes,
	})
}
View Source
var ApiIndexHandler = func(c *ksmux.Context) {
	m := map[string]TableEntity{}
	for _, t := range registeredTables {
		tb, _ := GetMemoryTable(t)
		m[t] = tb
	}
	if len(tableMethods) == 0 {
		c.Text("error: no method are allowed for the api")
		return
	}
	if len(registeredTables) == 0 {
		c.Text("error: no registered tables")
		return
	}
	c.Html("admin/api.html", map[string]any{
		"admin_path": adminPathNameGroup,
		"tables":     registeredTables,
		"tbMem":      m,
		"tbMethods":  tableMethods,
		"static_url": staticUrl,
		"EndWithSlash": func(str string) bool {
			q := []rune(str)
			return q[len(q)-1] == '/'
		},
	})
}
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 CreateModelView = func(c *ksmux.Context) {
	parseErr := c.Request.ParseMultipartForm(int64(ksmux.MultipartSize))
	if parseErr != nil {
		lg.ErrorC(parseErr.Error())
		return
	}
	data := c.Request.Form

	defer func() {
		err := c.Request.MultipartForm.RemoveAll()
		lg.CheckError(err)
	}()

	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]
		default:
			if key != "" && val[0] != "" && val[0] != "null" {
				m[key] = val[0]
			}
		}
	}
	_, err := Table(model).Database(defaultDB).Insert(m)
	if err != nil {
		lg.ErrorC("CreateModelView error", "err", err)
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": err.Error(),
		})
		return
	}

	c.Json(map[string]any{
		"success": "Done !",
	})
}
View Source
var DeleteRowPost = func(c *ksmux.Context) {
	data := c.BodyJson()
	if data["mission"] == "delete_row" {
		if model, ok := data["model_name"]; ok {
			if mm, ok := model.(string); ok {
				idString := "id"
				t, _ := GetMemoryTable(mm, defaultDB)
				if t.Pk != "" && t.Pk != "id" {
					idString = t.Pk
				}
				modelDB, err := Table(mm).Database(defaultDB).Where(idString+" = ?", data["id"]).One()
				if lg.CheckError(err) {
					lg.ErrorC("data received DeleteRowPost", "data", data)
					c.Status(http.StatusBadRequest).Json(map[string]any{
						"error": err.Error(),
					})
					return
				}
				if val, ok := modelDB["image"]; ok {
					if vv, ok := val.(string); ok && vv != "" {
						_ = c.DeleteFile(vv)
					}
				}

				if idS, ok := data["id"].(string); ok {
					_, err = Table(mm).Database(defaultDB).Where(idString+" = ?", idS).Delete()

					if err != nil {
						c.Status(http.StatusBadRequest).Json(map[string]any{
							"error": err.Error(),
						})
					} else {
						c.Json(map[string]any{
							"success": "Done !",
							"id":      data["id"],
						})
						return
					}
				}
			} else {
				c.Status(http.StatusBadRequest).Json(map[string]any{
					"error": "expecting model_name to be string",
				})
				return
			}
		} else {
			c.Status(http.StatusBadRequest).Json(map[string]any{
				"error": "no model_name found in request body",
			})
			return
		}
	}
}
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 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 IndexView = func(c *ksmux.Context) {
	allTables := GetAllTables(defaultDB)
	c.Html("admin/admin_index.html", map[string]any{
		"admin_path": adminPathNameGroup,
		"static_url": staticUrl,
		"tables":     allTables,
	})
}
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(500).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", map[string]any{
		"admin_path": adminPathNameGroup,
		"static_url": staticUrl,
	})
}
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{
		"admin_path": adminPathNameGroup,
		"static_url": staticUrl,
		"secure":     ksmux.IsTLS,
	}
	if v := lg.GetLogs(); v != nil {
		d["logs"] = reverseSlice[string](v.Slice)
	}
	c.Html("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 OfflineView = func(c *ksmux.Context) {
	c.Text("<h1>YOUR ARE OFFLINE, check connection</h1>")
}
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 SingleModelGet = func(c *ksmux.Context) {
	model := c.Param("model")
	if model == "" {
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": "param model not defined",
		})
		return
	}
	id := c.Param("id")
	if id == "" {
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": "param id not defined",
		})
		return
	}
	idString := "id"
	t, _ := GetMemoryTable(model, defaultDB)
	if t.Pk != "" && t.Pk != "id" {
		idString = t.Pk
	}

	modelRow, err := Table(model).Database(defaultDB).Where(idString+" = ?", id).One()
	if lg.CheckError(err) {
		c.Status(http.StatusBadRequest).Json(map[string]any{
			"error": err.Error(),
		})
		return
	}
	dbCols, _ := GetAllColumnsTypes(model)
	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 " + spTo[1] + " from " + spTo[0] + " order by " + spTo[1]
				mm, err := Table(spTo[0]).Database(defaultDB).QueryM(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
					}
				}
			}
		}
	}
	c.Html("admin/admin_single_model.html", map[string]any{
		"admin_path": adminPathNameGroup,
		"static_url": staticUrl,
		"model":      modelRow,
		"model_name": model,
		"id":         id,
		"fkeys":      mmfkeys,
		"columns":    t.ModelTypes,
		"dbcolumns":  dbCols,
		"pk":         t.Pk,
	})
}
View Source
var UpdateRowPost = func(c *ksmux.Context) {

	data, files := c.ParseMultipartForm()
	id := data["row_id"][0]
	idString := "id"
	t, _ := GetMemoryTable(data["table"][0], defaultDB)
	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{}
	for key, val := range data {
		if !SliceContains(ignored, key) {
			if modelDB[key] == val[0] {

				continue
			}
			toUpdate["`"+key+"`"] = 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
			}
		}
	}
	c.Json(map[string]string{
		"success": s + " updated successfully",
	})
}

Functions ¶

func AdaptNamedParams ¶ added in v1.8.0

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

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 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 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 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 EnableCheck ¶ added in v1.92.4

func EnableCheck()

EnableCheck enable struct changes check

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

FlushCache send msg to the cache system to Flush all the cache, safe to use in concurrent mode, and safe to use in general, it's done every 30 minutes(korm.FlushCacheEvery) and on update , create, delete , drop

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 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 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 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 func(database, table string, query string, args ...any) error)

func OnDrop ¶ added in v1.3.4

func OnDrop(fn func(database, table string) error)

func OnInsert ¶ added in v1.3.4

func OnInsert(fn DbHook)

func OnSet ¶ added in v1.3.4

func OnSet(fn DbHook)

func RegisterTable ¶ added in v1.4.7

func RegisterTable[T any](table TableRegistration[T], gendocs ...bool) error

func RemoveFromSlice ¶ added in v1.4.1

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

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 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 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 WithAPI ¶ added in v1.4.6

func WithAPI(rootPath string, middws ...func(handler ksmux.Handler) ksmux.Handler) *ksbus.Server

func WithBus ¶ added in v0.4.0

func WithBus(options ...ksbus.ServerOpts) *ksbus.Server

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

func WithDashboard ¶ added in v1.3.0

func WithDashboard(addr string, options ...DashOpts) *ksbus.Server

WithDashboard enable admin dashboard

func WithDocs ¶ added in v1.6.1

func WithDocs(generateJsonDocs bool, outJsonDocs string, handlerMiddlewares ...func(handler ksmux.Handler) ksmux.Handler) *ksbus.Server

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) *ksbus.Server

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) *ksbus.Server

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

func WithPprof ¶ added in v1.5.9

func WithPprof(path ...string) *ksbus.Server

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

func WithShell ¶ added in v1.5.3

func WithShell()

WithShell enable shell, go run main.go shell

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) 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)

Insert insert a row into a table and return inserted PK

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]) ToChan ¶ added in v1.8.5

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

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 > ?",1) 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       *ksbus.ServerOpts
	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-dashboard
}

type DatabaseEntity ¶

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

DatabaseEntity hold memory db state

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 DbHook ¶ added in v1.3.4

type DbHook func(database, table string, data map[string]any) error

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 ErrorHook ¶ added in v1.7.7

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

ErrorHook is the error handling callback signature

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 Hook ¶ added in v1.7.7

type Hook func(ctx context.Context, query string, args ...interface{}) (context.Context, error)

Hook is the hook callback signature

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 KV ¶ added in v1.9.6

type KV kstrct.KV

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

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

type TableRegistration ¶ added in v1.4.6

type TableRegistration[T any] struct {
	TableName     string
	Middws        []func(handler ksmux.Handler) ksmux.Handler
	Methods       []string
	BuilderGetAll func(modelBuilder *BuilderS[T]) *BuilderS[T]
	BuilderGetOne func(modelBuilder *BuilderS[T]) *BuilderS[T]
}

func (*TableRegistration[T]) HaveMethod ¶ added in v1.4.6

func (tr *TableRegistration[T]) HaveMethod(method string) bool

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"`
	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"`
}

Jump to

Keyboard shortcuts

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