A Simple and Powerful Golang SQL Library
 
 
 
Go to file
Vinícius Garcia ff4952da65 Partially update example_service to include the JSON feature
Its only partial because currently the function `structs.FillStructWith`
doesn't support the attributes tagged as `kissorm:"?,json"`

The task of updating the FillStructWith was updated to include this
behavior
2021-03-04 22:24:15 -03:00
examples Partially update example_service to include the JSON feature 2021-03-04 22:24:15 -03:00
nullable Add example tests to `examples/testing` 2021-01-03 13:41:21 -03:00
slices Fix lint problems 2020-09-14 15:55:54 -03:00
structs Add feature for automatically serialize fields tagged as "*,json" 2021-02-28 13:07:32 -03:00
.gitignore Add Makefile 2020-09-11 18:48:48 -03:00
Makefile Fix make setup recipe 2021-03-02 21:59:16 -03:00
README.md Partially update example_service to include the JSON feature 2021-03-04 22:24:15 -03:00
benchmark_test.go Add a Benchmark comparing us with the sqlx package 2021-01-22 21:25:14 -03:00
contracts.go Use errors.Wrap(sql.ErrNoRows, "..") to make ErrRecordNotFound more idiomatic 2021-01-17 20:27:12 -03:00
dialect.go Fix Insert function to work with postgres 2020-12-29 23:36:10 -03:00
docker-compose.yml Add docker-compose.yml to setup a postgres database for tests 2021-01-12 22:19:24 -03:00
go.mod Rerun `make setup` and `go mod tidy` 2021-03-02 22:00:34 -03:00
go.sum Rerun `make setup` and `go mod tidy` 2021-03-02 22:00:34 -03:00
json.go Update TestQueryOne to include the `json` feature 2021-03-02 22:16:29 -03:00
kiss_orm.go Update tests to work with a JSON field (currently only set to nil) 2021-02-28 20:48:03 -03:00
kiss_orm_test.go Update TestQueryChunks to include the new json feature 2021-03-02 22:49:20 -03:00
mocks.go Fix some comments so the linter stops complaining 2021-01-21 16:10:14 -03:00

README.md

KissORM

Welcome to the KissORM project, the Keep It Stupid Simple - ORM.

This ORM was created to be used by any developer efficiently and safely. The goals were:

  • To be easy to use
  • To be hard to make mistakes
  • To have a small API so it's easy to learn
  • To be easy to mock and test (very easy)
  • To be above all readable.

Supported Drivers:

Currently we only support 2 Drivers:

  • "postgres"
  • "sqlite3"

Why KissORM?

Note: If you want numbers see our Benchmark section below

KissORM was created to fill a hole between the complexity we find in the tools I've seen so far, namely:

  • ORMs such as GORM that do a lot and have literally hundreds of functions that require learning, increasing the risk of interpretation errors, learning time, complicating mocking, etc.
  • Tools such as sqlx that do little but still have most of the quirks from the standard sql lib, requiring several error checks for each query which is very low level.

Besides both these examples were not created having easy tests as one of the requisites, which might cause your team to loose far more time than necessary writing the tests or worst: Opting to not writing tests since it would take too much time.

So the goal was to be high level enough that it would avoid the complications from the sql package and at the same time to be simple enough to avoid the big learning curve and complexity of the hundreds of functions offered by more complete ORMs.

That said, KissORM attempts to apply the Kiss principle, in order to save development time for your team, i.e.:

  • Less time spent learning (few methods to learn)
  • Less time spent testing (helper tools made to help you)
  • less time spent debugging (simple apis are easier to debug)
  • and less time reading & understanding the code

Kiss Interface

The current interface is as follows and we plan to keep it with as little functions as possible, so don't expect many additions:

// ORMProvider describes the public behavior of this ORM
type ORMProvider interface {
	Insert(ctx context.Context, records ...interface{}) error
	Delete(ctx context.Context, ids ...interface{}) error
	Update(ctx context.Context, records ...interface{}) error

	Query(ctx context.Context, records interface{}, query string, params ...interface{}) error
	QueryOne(ctx context.Context, record interface{}, query string, params ...interface{}) error
	QueryChunks(ctx context.Context, parser ChunkParser) error

	Exec(ctx context.Context, query string, params ...interface{}) error
	Transaction(ctx context.Context, fn func(ORMProvider) error) error
}

Usage examples

This example is also available here if you want to compile it yourself.

package main

import (
	"context"
	"fmt"

	_ "github.com/mattn/go-sqlite3"
	"github.com/vingarcia/kissorm"
	"github.com/vingarcia/kissorm/nullable"
)

type User struct {
	ID      int     `kissorm:"id"`
	Name    string  `kissorm:"name"`
	Age     int     `kissorm:"age"`

	// This field will be saved as JSON in the database
	Address Address `kissorm:"address,json"`
}

type PartialUpdateUser struct {
	ID      int      `kissorm:"id"`
	Name    *string  `kissorm:"name"`
	Age     *int     `kissorm:"age"`
	Address *Address `kissorm:"address,json"`
}

type Address struct {
	State string `json:"state"`
	City  string `json:"city"`
}

func main() {
	ctx := context.Background()
	db, err := kissorm.New("sqlite3", "/tmp/hello.sqlite", 1, "users")
	if err != nil {
		panic(err.Error())
	}

	// In the definition below, please note that BLOB is
	// the only type we can use in sqlite for storing JSON.
	err = db.Exec(ctx, `CREATE TABLE IF NOT EXISTS users (
	  id INTEGER PRIMARY KEY,
		age INTEGER,
		name TEXT,
		address BLOB
	)`)
	if err != nil {
		panic(err.Error())
	}

	var alison = User{
		Name: "Alison",
		Age:  22,
		Address: Address{
			State: "MG",
		},
	}
	err = db.Insert(ctx, &alison)
	if err != nil {
		panic(err.Error())
	}
	fmt.Println("Alison ID:", alison.ID)

	// Inserting inline:
	err = db.Insert(ctx, &User{
		Name: "Cristina",
		Age:  27,
		Address: Address{
			State: "SP",
		},
	})
	if err != nil {
		panic(err.Error())
	}

	// Deleting Alison:
	err = db.Delete(ctx, alison.ID)
	if err != nil {
		panic(err.Error())
	}

	// Retrieving Cristina:
	var cris User
	err = db.QueryOne(ctx, &cris, "SELECT * FROM users WHERE name = ? ORDER BY id", "Cristina")
	if err != nil {
		panic(err.Error())
	}
	fmt.Printf("Cristina: %#v\n", cris)

	// Updating all fields from Cristina:
	cris.Name = "Cris"
	err = db.Update(ctx, cris)

	// Changing the age of Cristina but not touching any other fields:

	// Partial update technique 1:
	err = db.Update(ctx, struct {
		ID  int `kissorm:"id"`
		Age int `kissorm:"age"`
	}{ID: cris.ID, Age: 28})
	if err != nil {
		panic(err.Error())
	}

	// Partial update technique 2:
	err = db.Update(ctx, PartialUpdateUser{
		ID:  cris.ID,
		Age: nullable.Int(28),
	})
	if err != nil {
		panic(err.Error())
	}

	// Listing first 10 users from the database
	// (each time you run this example a new Cristina is created)
	//
	// Note: Using this function it is recommended to set a LIMIT, since
	// not doing so can load too many users on your computer's memory or
	// cause an Out Of Memory Kill.
	//
	// If you need to query very big numbers of users we recommend using
	// the `QueryChunks` function.
	var users []User
	err = db.Query(ctx, &users, "SELECT * FROM users LIMIT 10")
	if err != nil {
		panic(err.Error())
	}
	fmt.Printf("Users: %#v\n", users)
}

Testing Examples

This library has a few helper functions for helping your tests:

  • kissorm.FillStructWith(struct interface{}, dbRow map[string]interface{}) error
  • kissorm.FillSliceWith(structSlice interface{}, dbRows []map[string]interface{}) error
  • kissorm.StructToMap(struct interface{}) (map[string]interface{}, error)

If you want to see examples (we have examples for all the public functions) just read the example tests available on our example service

Benchmark Comparison

The benchmark is not bad, as far the code is in average as fast as sqlx:

$ make bench TIME=3s
go test -bench=. -benchtime=3s
goos: linux
goarch: amd64
pkg: github.com/vingarcia/kissorm
BenchmarkInsert/kissorm-setup/insert-one-4         	    4306	    880132 ns/op
BenchmarkInsert/sqlx-setup/insert-one-4            	    4573	    792488 ns/op
BenchmarkQuery/kissorm-setup/single-row-4          	   10000	    315328 ns/op
BenchmarkQuery/kissorm-setup/multiple-rows-4       	    9288	    388538 ns/op
BenchmarkQuery/sqlx-setup/single-row-4             	   10000	    323424 ns/op
BenchmarkQuery/sqlx-setup/multiple-rows-4          	   10000	    338570 ns/op
PASS
ok  	github.com/vingarcia/kissorm	21.740s

TODO List

  • Write documentation for the new feature of serializing structs as JSON
  • Implement support for nested objects with prefixed table names
  • Improve error messages
  • Add tests for tables using composite keys
  • Add support for serializing structs as other formats such as YAML
  • Update structs.FillStructWith to work with json tagged attributes

Optimizations Oportunities

  • Test if using a pointer on the field info is faster or not
  • Consider passing the cached structInfo as argument for all the functions that use it, so that we don't need to get it more than once in the same call.