From d6aa694f82fb74f19f881c3de0f8ca2b03bb778d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Garcia?= Date: Sun, 4 Dec 2022 20:39:29 -0300 Subject: [PATCH] Update README and the README build script --- Makefile | 6 +- README.md | 96 ++++++----- readme.template.md => README.template.md | 196 ++--------------------- examples/crud/crud.go | 27 +++- scripts/build-readme-from-template.go | 15 +- 5 files changed, 100 insertions(+), 240 deletions(-) rename readme.template.md => README.template.md (65%) diff --git a/Makefile b/Makefile index 0202c1b..02e97be 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ path=./... GOBIN=$(shell go env GOPATH)/bin -TIME=1s +TIME=5s test: setup go-mod-tidy $(GOBIN)/richgo test $(path) $(args) @@ -18,7 +18,9 @@ bench: go-mod-tidy @make --no-print-directory -C benchmarks TIME=$(TIME) | tee 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 - 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 @$(GOBIN)/staticcheck $(path) $(args) diff --git a/README.md b/README.md index e37256c..4f0908b 100644 --- a/README.md +++ b/README.md @@ -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 Report Card](https://goreportcard.com/badge/github.com/vingarcia/ksql) - # KSQL the Keep it Simple SQL library KSQL was created to offer an actually simple and satisfactory @@ -150,23 +149,35 @@ package main import ( "context" "fmt" + "time" "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 + // 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"` + + // 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 { ID int `ksql:"id"` Name *string `ksql:"name"` @@ -174,7 +185,6 @@ type PartialUpdateUser struct { Address *Address `ksql:"address,json"` } -// Address ... type Address struct { State string `json:"state"` City string `json:"city"` @@ -211,7 +221,9 @@ func main() { id INTEGER PRIMARY KEY, age INTEGER, name TEXT, - address BLOB + address BLOB, + created_at DATETIME, + updated_at DATETIME )`) if err != nil { panic(err.Error()) @@ -373,43 +385,43 @@ goos: linux goarch: amd64 pkg: github.com/vingarcia/ksql/benchmarks cpu: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz -BenchmarkInsert/ksql/sql-adapter/insert-one-12 9256 626985 ns/op -BenchmarkInsert/ksql/pgx-adapter/insert-one-12 11056 548748 ns/op -BenchmarkInsert/sql/insert-one-12 9565 623659 ns/op -BenchmarkInsert/sql/prep-stmt/insert-one-12 10000 541058 ns/op -BenchmarkInsert/sqlx/insert-one-12 9319 637775 ns/op -BenchmarkInsert/sqlx/prep-stmt/insert-one-12 10000 549806 ns/op -BenchmarkInsert/pgxpool/insert-one-12 10000 546349 ns/op -BenchmarkInsert/gorm/insert-one-12 8859 675650 ns/op -BenchmarkInsert/sqlc/insert-one-12 9889 634589 ns/op -BenchmarkInsert/sqlc/prep-stmt/insert-one-12 10000 552079 ns/op -BenchmarkInsert/sqlboiler/insert-one-12 9536 633515 ns/op -BenchmarkQuery/ksql/sql-adapter/single-row-12 41222 144799 ns/op -BenchmarkQuery/ksql/sql-adapter/multiple-rows-12 38523 156556 ns/op -BenchmarkQuery/ksql/pgx-adapter/single-row-12 85074 72465 ns/op -BenchmarkQuery/ksql/pgx-adapter/multiple-rows-12 70690 84502 ns/op -BenchmarkQuery/sql/single-row-12 41802 144467 ns/op -BenchmarkQuery/sql/multiple-rows-12 39248 147765 ns/op -BenchmarkQuery/sql/prep-stmt/single-row-12 80530 71376 ns/op -BenchmarkQuery/sql/prep-stmt/multiple-rows-12 76730 77769 ns/op -BenchmarkQuery/sqlx/single-row-12 41960 146817 ns/op -BenchmarkQuery/sqlx/multiple-rows-12 39349 152887 ns/op -BenchmarkQuery/sqlx/prep-stmt/single-row-12 81045 73004 ns/op -BenchmarkQuery/sqlx/prep-stmt/multiple-rows-12 75256 78604 ns/op -BenchmarkQuery/pgxpool/single-row-12 82630 72241 ns/op -BenchmarkQuery/pgxpool/multiple-rows-12 81619 74408 ns/op -BenchmarkQuery/gorm/single-row-12 76700 78651 ns/op -BenchmarkQuery/gorm/multiple-rows-12 62342 95746 ns/op -BenchmarkQuery/sqlc/single-row-12 41563 146143 ns/op -BenchmarkQuery/sqlc/multiple-rows-12 40240 149534 ns/op -BenchmarkQuery/sqlc/prep-stmt/single-row-12 83230 72397 ns/op -BenchmarkQuery/sqlc/prep-stmt/multiple-rows-12 79408 78645 ns/op -BenchmarkQuery/sqlboiler/single-row-12 65866 93841 ns/op -BenchmarkQuery/sqlboiler/multiple-rows-12 65091 94486 ns/op +BenchmarkInsert/ksql/sql-adapter/insert-one-12 9373 658434 ns/op +BenchmarkInsert/ksql/pgx-adapter/insert-one-12 10000 571623 ns/op +BenchmarkInsert/sql/insert-one-12 9423 627719 ns/op +BenchmarkInsert/sql/prep-stmt/insert-one-12 10000 559441 ns/op +BenchmarkInsert/sqlx/insert-one-12 9651 637823 ns/op +BenchmarkInsert/sqlx/prep-stmt/insert-one-12 10000 574260 ns/op +BenchmarkInsert/pgxpool/insert-one-12 10000 568112 ns/op +BenchmarkInsert/gorm/insert-one-12 8504 696791 ns/op +BenchmarkInsert/sqlc/insert-one-12 9504 662484 ns/op +BenchmarkInsert/sqlc/prep-stmt/insert-one-12 10000 568876 ns/op +BenchmarkInsert/sqlboiler/insert-one-12 9298 667913 ns/op +BenchmarkQuery/ksql/sql-adapter/single-row-12 39939 150412 ns/op +BenchmarkQuery/ksql/sql-adapter/multiple-rows-12 35901 156905 ns/op +BenchmarkQuery/ksql/pgx-adapter/single-row-12 83677 72461 ns/op +BenchmarkQuery/ksql/pgx-adapter/multiple-rows-12 71182 89788 ns/op +BenchmarkQuery/sql/single-row-12 40140 147991 ns/op +BenchmarkQuery/sql/multiple-rows-12 39210 154899 ns/op +BenchmarkQuery/sql/prep-stmt/single-row-12 82580 76769 ns/op +BenchmarkQuery/sql/prep-stmt/multiple-rows-12 76880 77115 ns/op +BenchmarkQuery/sqlx/single-row-12 42120 144501 ns/op +BenchmarkQuery/sqlx/multiple-rows-12 39396 155193 ns/op +BenchmarkQuery/sqlx/prep-stmt/single-row-12 84583 72094 ns/op +BenchmarkQuery/sqlx/prep-stmt/multiple-rows-12 75465 78078 ns/op +BenchmarkQuery/pgxpool/single-row-12 87724 72368 ns/op +BenchmarkQuery/pgxpool/multiple-rows-12 77012 77658 ns/op +BenchmarkQuery/gorm/single-row-12 74268 80303 ns/op +BenchmarkQuery/gorm/multiple-rows-12 63933 100220 ns/op +BenchmarkQuery/sqlc/single-row-12 39211 149178 ns/op +BenchmarkQuery/sqlc/multiple-rows-12 38748 153076 ns/op +BenchmarkQuery/sqlc/prep-stmt/single-row-12 83739 76111 ns/op +BenchmarkQuery/sqlc/prep-stmt/multiple-rows-12 75025 80939 ns/op +BenchmarkQuery/sqlboiler/single-row-12 63660 95534 ns/op +BenchmarkQuery/sqlboiler/multiple-rows-12 64256 98597 ns/op PASS -ok github.com/vingarcia/ksql/benchmarks 226.109s -Benchmark executed at: 2022-11-13 -Benchmark executed on commit: 5bfb5cd92affae29dab3499b07fcd36b70a20057 +ok github.com/vingarcia/ksql/benchmarks 224.967s +Benchmark executed at: 2022-12-04 +Benchmark executed on commit: e7896dc16ef8ede091e5d1568bd53096af65a1ef ``` ## Running the KSQL tests (for contributors) diff --git a/readme.template.md b/README.template.md similarity index 65% rename from readme.template.md rename to README.template.md index 60e1093..15ea5ce 100644 --- a/readme.template.md +++ b/README.template.md @@ -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 Report Card](https://goreportcard.com/badge/github.com/vingarcia/ksql) - # KSQL the Keep it Simple SQL library KSQL was created to offer an actually simple and satisfactory tool for interacting with SQL Databases in Golang. -The core idea on KSQL is to offer an easy to use interface, -the actual communication with the database is decoupled so we can use -KSQL on top of `pgx`, `database/sql` and possibly other tools. -You can even create you own backend adapter for KSQL which is +The core goal of KSQL is not to offer new features that +are unavailable on other libraries (although we do have some), +but to offer a well-thought and well-planned API so that users +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. 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. ```Go -package main - -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()) - } -} +{{ .crudExample -}} ``` ## Benchmark Comparison @@ -362,7 +188,7 @@ Without further ado, here are the results: ```bash $ make bench TIME=5s -{{- .benchmark -}} +{{ .benchmark -}} ``` ## Running the KSQL tests (for contributors) diff --git a/examples/crud/crud.go b/examples/crud/crud.go index ab84fbe..aa69f26 100644 --- a/examples/crud/crud.go +++ b/examples/crud/crud.go @@ -3,23 +3,35 @@ package main import ( "context" "fmt" + "time" "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 + // 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"` + + // 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 { ID int `ksql:"id"` Name *string `ksql:"name"` @@ -27,13 +39,12 @@ type PartialUpdateUser struct { 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 +// 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") @@ -64,7 +75,9 @@ func main() { id INTEGER PRIMARY KEY, age INTEGER, name TEXT, - address BLOB + address BLOB, + created_at DATETIME, + updated_at DATETIME )`) if err != nil { panic(err.Error()) @@ -102,7 +115,7 @@ func main() { } // 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 err = db.QueryOne(ctx, &cris, "FROM users WHERE name = ? ORDER BY id", "Cristina") if err != nil { diff --git a/scripts/build-readme-from-template.go b/scripts/build-readme-from-template.go index af9dfa2..74b4163 100755 --- a/scripts/build-readme-from-template.go +++ b/scripts/build-readme-from-template.go @@ -7,14 +7,15 @@ import ( ) func main() { - if len(os.Args) < 3 { + if len(os.Args) < 4 { 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] - benchmarkFilepath := os.Args[2] + crudExampleFilepath := os.Args[2] + benchmarkFilepath := os.Args[3] data, err := os.ReadFile(templateFilepath) if err != nil { @@ -26,6 +27,11 @@ func main() { 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) if err != nil { log.Fatalf("unable to read benchmark results '%s': %s", benchmarkFilepath, err) @@ -37,7 +43,8 @@ func main() { } err = t.Execute(f, map[string]interface{}{ - "benchmark": string(benchmark), + "crudExample": string(crudExample), + "benchmark": string(benchmark), }) if err != nil { log.Fatalf("error executing template file: %s", err)