Update README and the README build script

pull/32/head
Vinícius Garcia 2022-12-04 20:39:29 -03:00
parent e7896dc16e
commit d6aa694f82
5 changed files with 100 additions and 240 deletions

View File

@ -3,7 +3,7 @@ path=./...
GOBIN=$(shell go env GOPATH)/bin GOBIN=$(shell go env GOPATH)/bin
TIME=1s TIME=5s
test: setup go-mod-tidy test: setup go-mod-tidy
$(GOBIN)/richgo test $(path) $(args) $(GOBIN)/richgo test $(path) $(args)
@ -18,7 +18,9 @@ bench: go-mod-tidy
@make --no-print-directory -C benchmarks TIME=$(TIME) | tee benchmark.tmp @make --no-print-directory -C benchmarks TIME=$(TIME) | tee benchmark.tmp
@echo "Benchmark executed at: $$(date --iso)" | tee -a benchmark.tmp @echo "Benchmark executed at: $$(date --iso)" | tee -a benchmark.tmp
@echo "Benchmark executed on commit: $$(git rev-parse HEAD)" | tee -a benchmark.tmp @echo "Benchmark executed on commit: $$(git rev-parse HEAD)" | tee -a benchmark.tmp
go run scripts/build-readme-from-template.go readme.template.md benchmark.tmp
readme: benchmark.tmp README.template.md
go run scripts/build-readme-from-template.go README.template.md examples/crud/crud.go benchmark.tmp
lint: setup go-mod-tidy lint: setup go-mod-tidy
@$(GOBIN)/staticcheck $(path) $(args) @$(GOBIN)/staticcheck $(path) $(args)

View File

@ -3,7 +3,6 @@
[![Go Reference](https://pkg.go.dev/badge/github.com/vingarcia/ksql.svg)](https://pkg.go.dev/github.com/vingarcia/ksql) [![Go Reference](https://pkg.go.dev/badge/github.com/vingarcia/ksql.svg)](https://pkg.go.dev/github.com/vingarcia/ksql)
![Go Report Card](https://goreportcard.com/badge/github.com/vingarcia/ksql) ![Go Report Card](https://goreportcard.com/badge/github.com/vingarcia/ksql)
# KSQL the Keep it Simple SQL library # KSQL the Keep it Simple SQL library
KSQL was created to offer an actually simple and satisfactory KSQL was created to offer an actually simple and satisfactory
@ -150,23 +149,35 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/vingarcia/ksql" "github.com/vingarcia/ksql"
"github.com/vingarcia/ksql/adapters/ksqlite3" "github.com/vingarcia/ksql/adapters/ksqlite3"
"github.com/vingarcia/ksql/nullable" "github.com/vingarcia/ksql/nullable"
) )
// User ...
type User struct { type User struct {
ID int `ksql:"id"` ID int `ksql:"id"`
Name string `ksql:"name"` Name string `ksql:"name"`
Age int `ksql:"age"` Age int `ksql:"age"`
// This field will be saved as JSON in the database // The following attributes are making use of the KSQL Modifiers,
// you can find more about them on our Wiki:
//
// - https://github.com/VinGarcia/ksql/wiki/Modifiers
//
// The `json` modifier will save the address as JSON in the database
Address Address `ksql:"address,json"` Address Address `ksql:"address,json"`
// The timeNowUTC modifier will set this field to `time.Now().UTC()` before saving it:
UpdatedAt time.Time `ksql:"updated_at,timeNowUTC"`
// The timeNowUTC/skipUpdates modifier will set this field to `time.Now().UTC()` only
// when first creating it and ignore it during updates.
CreatedAt time.Time `ksql:"created_at,timeNowUTC/skipUpdates"`
} }
// PartialUpdateUser ...
type PartialUpdateUser struct { type PartialUpdateUser struct {
ID int `ksql:"id"` ID int `ksql:"id"`
Name *string `ksql:"name"` Name *string `ksql:"name"`
@ -174,7 +185,6 @@ type PartialUpdateUser struct {
Address *Address `ksql:"address,json"` Address *Address `ksql:"address,json"`
} }
// Address ...
type Address struct { type Address struct {
State string `json:"state"` State string `json:"state"`
City string `json:"city"` City string `json:"city"`
@ -211,7 +221,9 @@ func main() {
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
age INTEGER, age INTEGER,
name TEXT, name TEXT,
address BLOB address BLOB,
created_at DATETIME,
updated_at DATETIME
)`) )`)
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
@ -373,43 +385,43 @@ goos: linux
goarch: amd64 goarch: amd64
pkg: github.com/vingarcia/ksql/benchmarks pkg: github.com/vingarcia/ksql/benchmarks
cpu: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz cpu: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz
BenchmarkInsert/ksql/sql-adapter/insert-one-12 9256 626985 ns/op BenchmarkInsert/ksql/sql-adapter/insert-one-12 9373 658434 ns/op
BenchmarkInsert/ksql/pgx-adapter/insert-one-12 11056 548748 ns/op BenchmarkInsert/ksql/pgx-adapter/insert-one-12 10000 571623 ns/op
BenchmarkInsert/sql/insert-one-12 9565 623659 ns/op BenchmarkInsert/sql/insert-one-12 9423 627719 ns/op
BenchmarkInsert/sql/prep-stmt/insert-one-12 10000 541058 ns/op BenchmarkInsert/sql/prep-stmt/insert-one-12 10000 559441 ns/op
BenchmarkInsert/sqlx/insert-one-12 9319 637775 ns/op BenchmarkInsert/sqlx/insert-one-12 9651 637823 ns/op
BenchmarkInsert/sqlx/prep-stmt/insert-one-12 10000 549806 ns/op BenchmarkInsert/sqlx/prep-stmt/insert-one-12 10000 574260 ns/op
BenchmarkInsert/pgxpool/insert-one-12 10000 546349 ns/op BenchmarkInsert/pgxpool/insert-one-12 10000 568112 ns/op
BenchmarkInsert/gorm/insert-one-12 8859 675650 ns/op BenchmarkInsert/gorm/insert-one-12 8504 696791 ns/op
BenchmarkInsert/sqlc/insert-one-12 9889 634589 ns/op BenchmarkInsert/sqlc/insert-one-12 9504 662484 ns/op
BenchmarkInsert/sqlc/prep-stmt/insert-one-12 10000 552079 ns/op BenchmarkInsert/sqlc/prep-stmt/insert-one-12 10000 568876 ns/op
BenchmarkInsert/sqlboiler/insert-one-12 9536 633515 ns/op BenchmarkInsert/sqlboiler/insert-one-12 9298 667913 ns/op
BenchmarkQuery/ksql/sql-adapter/single-row-12 41222 144799 ns/op BenchmarkQuery/ksql/sql-adapter/single-row-12 39939 150412 ns/op
BenchmarkQuery/ksql/sql-adapter/multiple-rows-12 38523 156556 ns/op BenchmarkQuery/ksql/sql-adapter/multiple-rows-12 35901 156905 ns/op
BenchmarkQuery/ksql/pgx-adapter/single-row-12 85074 72465 ns/op BenchmarkQuery/ksql/pgx-adapter/single-row-12 83677 72461 ns/op
BenchmarkQuery/ksql/pgx-adapter/multiple-rows-12 70690 84502 ns/op BenchmarkQuery/ksql/pgx-adapter/multiple-rows-12 71182 89788 ns/op
BenchmarkQuery/sql/single-row-12 41802 144467 ns/op BenchmarkQuery/sql/single-row-12 40140 147991 ns/op
BenchmarkQuery/sql/multiple-rows-12 39248 147765 ns/op BenchmarkQuery/sql/multiple-rows-12 39210 154899 ns/op
BenchmarkQuery/sql/prep-stmt/single-row-12 80530 71376 ns/op BenchmarkQuery/sql/prep-stmt/single-row-12 82580 76769 ns/op
BenchmarkQuery/sql/prep-stmt/multiple-rows-12 76730 77769 ns/op BenchmarkQuery/sql/prep-stmt/multiple-rows-12 76880 77115 ns/op
BenchmarkQuery/sqlx/single-row-12 41960 146817 ns/op BenchmarkQuery/sqlx/single-row-12 42120 144501 ns/op
BenchmarkQuery/sqlx/multiple-rows-12 39349 152887 ns/op BenchmarkQuery/sqlx/multiple-rows-12 39396 155193 ns/op
BenchmarkQuery/sqlx/prep-stmt/single-row-12 81045 73004 ns/op BenchmarkQuery/sqlx/prep-stmt/single-row-12 84583 72094 ns/op
BenchmarkQuery/sqlx/prep-stmt/multiple-rows-12 75256 78604 ns/op BenchmarkQuery/sqlx/prep-stmt/multiple-rows-12 75465 78078 ns/op
BenchmarkQuery/pgxpool/single-row-12 82630 72241 ns/op BenchmarkQuery/pgxpool/single-row-12 87724 72368 ns/op
BenchmarkQuery/pgxpool/multiple-rows-12 81619 74408 ns/op BenchmarkQuery/pgxpool/multiple-rows-12 77012 77658 ns/op
BenchmarkQuery/gorm/single-row-12 76700 78651 ns/op BenchmarkQuery/gorm/single-row-12 74268 80303 ns/op
BenchmarkQuery/gorm/multiple-rows-12 62342 95746 ns/op BenchmarkQuery/gorm/multiple-rows-12 63933 100220 ns/op
BenchmarkQuery/sqlc/single-row-12 41563 146143 ns/op BenchmarkQuery/sqlc/single-row-12 39211 149178 ns/op
BenchmarkQuery/sqlc/multiple-rows-12 40240 149534 ns/op BenchmarkQuery/sqlc/multiple-rows-12 38748 153076 ns/op
BenchmarkQuery/sqlc/prep-stmt/single-row-12 83230 72397 ns/op BenchmarkQuery/sqlc/prep-stmt/single-row-12 83739 76111 ns/op
BenchmarkQuery/sqlc/prep-stmt/multiple-rows-12 79408 78645 ns/op BenchmarkQuery/sqlc/prep-stmt/multiple-rows-12 75025 80939 ns/op
BenchmarkQuery/sqlboiler/single-row-12 65866 93841 ns/op BenchmarkQuery/sqlboiler/single-row-12 63660 95534 ns/op
BenchmarkQuery/sqlboiler/multiple-rows-12 65091 94486 ns/op BenchmarkQuery/sqlboiler/multiple-rows-12 64256 98597 ns/op
PASS PASS
ok github.com/vingarcia/ksql/benchmarks 226.109s ok github.com/vingarcia/ksql/benchmarks 224.967s
Benchmark executed at: 2022-11-13 Benchmark executed at: 2022-12-04
Benchmark executed on commit: 5bfb5cd92affae29dab3499b07fcd36b70a20057 Benchmark executed on commit: e7896dc16ef8ede091e5d1568bd53096af65a1ef
``` ```
## Running the KSQL tests (for contributors) ## Running the KSQL tests (for contributors)

View File

@ -3,16 +3,20 @@
[![Go Reference](https://pkg.go.dev/badge/github.com/vingarcia/ksql.svg)](https://pkg.go.dev/github.com/vingarcia/ksql) [![Go Reference](https://pkg.go.dev/badge/github.com/vingarcia/ksql.svg)](https://pkg.go.dev/github.com/vingarcia/ksql)
![Go Report Card](https://goreportcard.com/badge/github.com/vingarcia/ksql) ![Go Report Card](https://goreportcard.com/badge/github.com/vingarcia/ksql)
# KSQL the Keep it Simple SQL library # KSQL the Keep it Simple SQL library
KSQL was created to offer an actually simple and satisfactory KSQL was created to offer an actually simple and satisfactory
tool for interacting with SQL Databases in Golang. tool for interacting with SQL Databases in Golang.
The core idea on KSQL is to offer an easy to use interface, The core goal of KSQL is not to offer new features that
the actual communication with the database is decoupled so we can use are unavailable on other libraries (although we do have some),
KSQL on top of `pgx`, `database/sql` and possibly other tools. but to offer a well-thought and well-planned API so that users
You can even create you own backend adapter for KSQL which is have an easier time, learning, debugging, and avoiding common pitfalls.
KSQL is also decoupled from its backend so that
the actual communication with the database is performed by
well-known and trusted technologies, namely: `pgx` and `database/sql`.
You can even create your own backend adapter for KSQL which is
useful in some situations. useful in some situations.
In this README you will find examples for "Getting Started" with the library, In this README you will find examples for "Getting Started" with the library,
@ -140,185 +144,7 @@ which is also available [here](./examples/crud/crud.go)
if you want to compile it yourself. if you want to compile it yourself.
```Go ```Go
package main {{ .crudExample -}}
import (
"context"
"fmt"
"github.com/vingarcia/ksql"
"github.com/vingarcia/ksql/adapters/ksqlite3"
"github.com/vingarcia/ksql/nullable"
)
// User ...
type User struct {
ID int `ksql:"id"`
Name string `ksql:"name"`
Age int `ksql:"age"`
// This field will be saved as JSON in the database
Address Address `ksql:"address,json"`
}
// PartialUpdateUser ...
type PartialUpdateUser struct {
ID int `ksql:"id"`
Name *string `ksql:"name"`
Age *int `ksql:"age"`
Address *Address `ksql:"address,json"`
}
// Address ...
type Address struct {
State string `json:"state"`
City string `json:"city"`
}
// UsersTable informs KSQL the name of the table and that it can
// use the default value for the primary key column name: "id"
var UsersTable = ksql.NewTable("users")
func main() {
ctx := context.Background()
// The available adapters are:
// - kpgx.New(ctx, connURL, ksql.Config{})
// - kmysql.New(ctx, connURL, ksql.Config{})
// - ksqlserver.New(ctx, connURL, ksql.Config{})
// - ksqlite3.New(ctx, connURL, ksql.Config{})
//
// For more detailed examples see:
// - `./examples/all_adapters/all_adapters.go`
//
// In this example we'll use sqlite3:
db, err := ksqlite3.New(ctx, "/tmp/hello.sqlite", ksql.Config{
MaxOpenConns: 1,
})
if err != nil {
panic(err.Error())
}
defer db.Close()
// 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, UsersTable, &alison)
if err != nil {
panic(err.Error())
}
fmt.Println("Alison ID:", alison.ID)
// Inserting inline:
err = db.Insert(ctx, UsersTable, &User{
Name: "Cristina",
Age: 27,
Address: Address{
State: "SP",
},
})
if err != nil {
panic(err.Error())
}
// Deleting Alison:
err = db.Delete(ctx, UsersTable, alison.ID)
if err != nil {
panic(err.Error())
}
// Retrieving Cristina, note that if you omit the SELECT part of the query
// KSQL will build it for you (efficiently) based on the fields from the struct:
var cris User
err = db.QueryOne(ctx, &cris, "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.Patch(ctx, UsersTable, cris)
// Changing the age of Cristina but not touching any other fields:
// Partial update technique 1:
err = db.Patch(ctx, UsersTable, struct {
ID int `ksql:"id"`
Age int `ksql:"age"`
}{ID: cris.ID, Age: 28})
if err != nil {
panic(err.Error())
}
// Partial update technique 2:
err = db.Patch(ctx, UsersTable, PartialUpdateUser{
ID: cris.ID,
Age: nullable.Int(28), // (just a pointer to an int, if null it won't be updated)
})
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, "FROM users LIMIT 10")
if err != nil {
panic(err.Error())
}
fmt.Printf("Users: %#v\n", users)
// Making transactions:
err = db.Transaction(ctx, func(db ksql.Provider) error {
var cris2 User
err = db.QueryOne(ctx, &cris2, "FROM users WHERE id = ?", cris.ID)
if err != nil {
// This will cause an automatic rollback:
return err
}
err = db.Patch(ctx, UsersTable, PartialUpdateUser{
ID: cris2.ID,
Age: nullable.Int(29),
})
if err != nil {
// This will also cause an automatic rollback and then panic again
// so that we don't hide the panic inside the KSQL library
panic(err.Error())
}
// Commits the transaction
return nil
})
if err != nil {
panic(err.Error())
}
}
``` ```
## Benchmark Comparison ## Benchmark Comparison
@ -362,7 +188,7 @@ Without further ado, here are the results:
```bash ```bash
$ make bench TIME=5s $ make bench TIME=5s
{{- .benchmark -}} {{ .benchmark -}}
``` ```
## Running the KSQL tests (for contributors) ## Running the KSQL tests (for contributors)

View File

@ -3,23 +3,35 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/vingarcia/ksql" "github.com/vingarcia/ksql"
"github.com/vingarcia/ksql/adapters/ksqlite3" "github.com/vingarcia/ksql/adapters/ksqlite3"
"github.com/vingarcia/ksql/nullable" "github.com/vingarcia/ksql/nullable"
) )
// User ...
type User struct { type User struct {
ID int `ksql:"id"` ID int `ksql:"id"`
Name string `ksql:"name"` Name string `ksql:"name"`
Age int `ksql:"age"` Age int `ksql:"age"`
// This field will be saved as JSON in the database // The following attributes are making use of the KSQL Modifiers,
// you can find more about them on our Wiki:
//
// - https://github.com/VinGarcia/ksql/wiki/Modifiers
//
// The `json` modifier will save the address as JSON in the database
Address Address `ksql:"address,json"` Address Address `ksql:"address,json"`
// The timeNowUTC modifier will set this field to `time.Now().UTC()` before saving it:
UpdatedAt time.Time `ksql:"updated_at,timeNowUTC"`
// The timeNowUTC/skipUpdates modifier will set this field to `time.Now().UTC()` only
// when first creating it and ignore it during updates.
CreatedAt time.Time `ksql:"created_at,timeNowUTC/skipUpdates"`
} }
// PartialUpdateUser ...
type PartialUpdateUser struct { type PartialUpdateUser struct {
ID int `ksql:"id"` ID int `ksql:"id"`
Name *string `ksql:"name"` Name *string `ksql:"name"`
@ -27,13 +39,12 @@ type PartialUpdateUser struct {
Address *Address `ksql:"address,json"` Address *Address `ksql:"address,json"`
} }
// Address ...
type Address struct { type Address struct {
State string `json:"state"` State string `json:"state"`
City string `json:"city"` City string `json:"city"`
} }
// UsersTable informs ksql the name of the table and that it can // UsersTable informs KSQL the name of the table and that it can
// use the default value for the primary key column name: "id" // use the default value for the primary key column name: "id"
var UsersTable = ksql.NewTable("users") var UsersTable = ksql.NewTable("users")
@ -64,7 +75,9 @@ func main() {
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
age INTEGER, age INTEGER,
name TEXT, name TEXT,
address BLOB address BLOB,
created_at DATETIME,
updated_at DATETIME
)`) )`)
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
@ -102,7 +115,7 @@ func main() {
} }
// Retrieving Cristina, note that if you omit the SELECT part of the query // Retrieving Cristina, note that if you omit the SELECT part of the query
// ksql will build it for you (efficiently) based on the fields from the struct: // KSQL will build it for you (efficiently) based on the fields from the struct:
var cris User var cris User
err = db.QueryOne(ctx, &cris, "FROM users WHERE name = ? ORDER BY id", "Cristina") err = db.QueryOne(ctx, &cris, "FROM users WHERE name = ? ORDER BY id", "Cristina")
if err != nil { if err != nil {

View File

@ -7,14 +7,15 @@ import (
) )
func main() { func main() {
if len(os.Args) < 3 { if len(os.Args) < 4 {
log.Fatalf( log.Fatalf(
"USAGE: go run scripts/build-readme-from-template.go TEMPLATE_FILEPATH BENCHMARK_FILEPATH", "USAGE: go run scripts/build-readme-from-template.go PATH_TO_TEMPLATE PATH_TO_CRUD_EXAMPLE PATH_TO_BENCHMARK",
) )
} }
templateFilepath := os.Args[1] templateFilepath := os.Args[1]
benchmarkFilepath := os.Args[2] crudExampleFilepath := os.Args[2]
benchmarkFilepath := os.Args[3]
data, err := os.ReadFile(templateFilepath) data, err := os.ReadFile(templateFilepath)
if err != nil { if err != nil {
@ -26,6 +27,11 @@ func main() {
log.Fatalf("unable to parse README template '%s': %s", templateFilepath, err) log.Fatalf("unable to parse README template '%s': %s", templateFilepath, err)
} }
crudExample, err := os.ReadFile(crudExampleFilepath)
if err != nil {
log.Fatalf("unable to read benchmark results '%s': %s", benchmarkFilepath, err)
}
benchmark, err := os.ReadFile(benchmarkFilepath) benchmark, err := os.ReadFile(benchmarkFilepath)
if err != nil { if err != nil {
log.Fatalf("unable to read benchmark results '%s': %s", benchmarkFilepath, err) log.Fatalf("unable to read benchmark results '%s': %s", benchmarkFilepath, err)
@ -37,7 +43,8 @@ func main() {
} }
err = t.Execute(f, map[string]interface{}{ err = t.Execute(f, map[string]interface{}{
"benchmark": string(benchmark), "crudExample": string(crudExample),
"benchmark": string(benchmark),
}) })
if err != nil { if err != nil {
log.Fatalf("error executing template file: %s", err) log.Fatalf("error executing template file: %s", err)