From 19b92b7f3f1b19a7c4bb934c910dde06f18ca0c4 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Fri, 10 Dec 2021 16:44:15 +0700 Subject: [PATCH 1/3] docs: add proper information and introductions --- README.md | 71 +++++++++++++++++++++----- bob.go | 23 +++++++++ example_test.go | 132 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 14 deletions(-) create mode 100644 example_test.go diff --git a/README.md b/README.md index e0c8f80..7c1d42f 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,51 @@ # Bob - SQL Query Builder -[![Go Reference](https://pkg.go.dev/badge/github.com/aldy505/bob.svg)](https://pkg.go.dev/github.com/aldy505/bob) [![Go Report Card](https://goreportcard.com/badge/github.com/aldy505/bob)](https://goreportcard.com/report/github.com/aldy505/bob) ![GitHub](https://img.shields.io/github/license/aldy505/bob) [![CodeFactor](https://www.codefactor.io/repository/github/aldy505/bob/badge)](https://www.codefactor.io/repository/github/aldy505/bob) [![codecov](https://codecov.io/gh/aldy505/bob/branch/master/graph/badge.svg?token=Noeexg5xEJ)](https://codecov.io/gh/aldy505/bob) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/9b78970127c74c1a923533e05f65848d)](https://www.codacy.com/gh/aldy505/bob/dashboard?utm_source=github.com&utm_medium=referral&utm_content=aldy505/bob&utm_campaign=Badge_Grade) [![Build test](https://github.com/aldy505/bob/actions/workflows/build.yml/badge.svg)](https://github.com/aldy505/bob/actions/workflows/build.yml) [![Test and coverage](https://github.com/aldy505/bob/actions/workflows/coverage.yml/badge.svg)](https://github.com/aldy505/bob/actions/workflows/coverage.yml) +[![Go Reference](pkg-go-dev-badge)](pkg-go-dev-link) +[![Go Report Card](go-report-badge)](go-report-link) +![GitHub](license-badge) +[![CodeFactor](codefactor-badge)](codefactor-link) +[![codecov](codecov-badge)](codecov-link) +[![Codacy Badge](codacy-badge)](codacy-link) +[![Test and coverage](actions-badge)](actions-link) -Think of this as an extension of [Squirrel](https://github.com/Masterminds/squirrel) with functionability like [Knex](https://knexjs.org/). I still use Squirrel for other types of queries (insert, select, and all that), but I needed some SQL builder for create table and some other stuffs. Including database creation & upsert. +Bob is an SQL builder library initially made as an extension for [Squirrel](squirrel-url) +with functionality like [Knex](knex-url) (from the Node.js world). Squirrel itself +doesn't provide other types of queries for creating a table, upsert, +and some other things. Bob is meant to fill those gaps. -Oh, and of course, heavily inspired by [Bob the Builder](https://en.wikipedia.org/wiki/Bob_the_Builder). +The different between Bob and Squirrel is that Bob is solely a query builder. +The users have to execute and manage the SQL connection themself. +Meaning there are no ExecWith() function implemented on Bob, as you can +find it on Squirrel. + +The purpose of an SQL query builder is to prevent any typo or mistypes +on the SQL queries. Although also with that reason, Bob might not always +have the right query for you, depending on what you are doing with the +SQL query. It might sometimes be better for you to write the SQL query +yourself, if your problem is specific and needs some micro-tweaks. + +With that being said, I hope you enjoy using Bob and consider starring or +reporting any issues regarding the usage of Bob in your projects. + +Oh, and of course, heavily inspired by [Bob the Builder](bob-wikipedia). + +## Usage ```go import "github.com/aldy505/bob" ``` -## Usage +Like any other Go projects when you're using Go modules, just put that +text right there on the top of your projects, do `go mod tidy` and +you are good to go. -It's not ready for large-scale production yet (although I've already using it on one of my projects). But, the API is probably close to how you'd do things on Squirrel. +Either way, I'm not 100% confident enough to say that this thing is +production ready. But, the way I see it, it's good enough to be used +on a production-level applications. In fact, I'm using it on one of my +current projects that's getting around 100-200 hits per day. + +If you have any feature request or improvement ideas for the project, +please kindly open an issue ### Create a table @@ -177,7 +210,7 @@ func main() { Key("email"). Replace("age", 40). ToSql() - + // One more time, for MSSQL / SQL Server. sql, args, err = bob. Upsert("users", bob.MSSQL). @@ -276,7 +309,7 @@ func main() { if err != nil { log.Fatal(err) } - + _, err = db.Query(context.Background(), inventoryQuery[i]) if err != nil { log.Fatal(err) @@ -299,13 +332,6 @@ func main() { * `bob.Truncate(tableName)` - Truncate a table (`truncate "users"`) * `bob.Upsert(tableName, dialect)` - UPSERT function (`insert into "users" ("name", "email") values (?, ?) on duplicate key update email = ?`) -### TODO - -Meaning these are some ideas for the future development of Bob. - -* `bob.ExecWith()` - Just like Squirrel's [ExecWith](https://pkg.go.dev/github.com/Masterminds/squirrel?utm_source=godoc#ExecWith) -* `bob.Count(tableName, columnName)` - Count query (`select count("active") from "users"`) - ## Contributing Contributions are always welcome! As long as you add a test for your changes. @@ -313,3 +339,20 @@ Contributions are always welcome! As long as you add a test for your changes. ## License Bob is licensed under [MIT license](./LICENSE) + +[squirrel-url]: https://github.com/Masterminds/squirrel +[knex-url]: https://knexjs.org/ +[bob-wikipedia]: https://en.wikipedia.org/wiki/Bob_the_Builder +[pkg-go-dev-badge]: https://pkg.go.dev/badge/github.com/aldy505/bob.svg +[pkg-go-dev-link]: https://pkg.go.dev/github.com/aldy505/bob +[go-report-badge]: https://goreportcard.com/badge/github.com/aldy505/bob +[go-report-link]: https://goreportcard.com/report/github.com/aldy505/bob +[license-badge]: https://img.shields.io/github/license/aldy505/bob +[codefactor-link]: https://www.codefactor.io/repository/github/aldy505/bob +[codefactor-badge]: https://www.codefactor.io/repository/github/aldy505/bob/badge +[codecov-badge]: https://codecov.io/gh/aldy505/bob/branch/master/graph/badge.svg?token=Noeexg5xEJ +[codecov-link]: https://codecov.io/gh/aldy505/bob +[codacy-badge]: https://app.codacy.com/project/badge/Grade/9b78970127c74c1a923533e05f65848d +[codacy-link]: https://www.codacy.com/gh/aldy505/bob/dashboard?utm_source=github.com&utm_medium=referral&utm_content=aldy505/bob&utm_campaign=Badge_Grade +[actions-badge]: https://github.com/aldy505/bob/actions/workflows/coverage.yml/badge.svg +[actions-link]: https://github.com/aldy505/bob/actions/workflows/coverage.yml diff --git a/bob.go b/bob.go index 26c86a0..ad9c161 100644 --- a/bob.go +++ b/bob.go @@ -1,3 +1,26 @@ +// Bob is an SQL builder library initially made as an extension for Squirrel +// with functionality like Knex (from the Node.js world). Squirrel itself +// doesn't provide other types of queries for creating a table, upsert, +// and some other things. Bob is meant to fill those gaps. +// +// The different between Bob and Squirrel is that Bob is solely a query builder. +// The users have to execute and manage the SQL connection themself. +// Meaning there are no ExecWith() function implemented on Bob, as you can +// find it on Squirrel. +// +// The purpose of an SQL query builder is to prevent any typo or mistypes +// on the SQL queries. Although also with that reason, Bob might not always +// have the right query for you, depending on what you are doing with the +// SQL query. It might sometimes be better for you to write the SQL query +// yourself, if your problem is specific and needs some micro-tweaks. +// +// With that being said, I hope you enjoy using Bob and consider starring or +// reporting any issues regarding the usage of Bob in your projects. +// +// MIT License +// +// Copyright (c) 2021-present Reinaldy Rafli and Bob collaborators +// package bob import ( diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..a62a31d --- /dev/null +++ b/example_test.go @@ -0,0 +1,132 @@ +package bob_test + +import ( + "fmt" + "log" + + "github.com/aldy505/bob" +) + +func ExampleCreateIndex() { + sql, _, err := bob. + CreateIndex("idx_email"). + On("users"). + Unique(). + Columns(bob.IndexColumn{Name: "email", Collate: "DEFAULT", Extras: []string{"ASC"}}). + ToSql() + if err != nil { + fmt.Printf("Handle this error: %v", err) + } + + fmt.Print(sql) + // Output: CREATE UNIQUE INDEX idx_email ON users (email COLLATE DEFAULT ASC); +} + +func ExampleHasTable() { + sql, args, err := bob.HasTable("users").ToSql() + if err != nil { + fmt.Printf("Handle this error: %v", err) + } + + fmt.Printf("sql: %s, args: %v", sql, args) + // Output: sql: SELECT * FROM information_schema.tables WHERE table_name = ? AND table_schema = current_schema();, args: [users] +} + +func ExampleHasColumn() { + sql, args, err := bob.HasColumn("email").ToSql() + if err != nil { + fmt.Printf("Handle this error: %v", err) + } + + fmt.Printf("sql: %s, args: %v", sql, args) + // Output: sql: SELECT * FROM information_schema.columns WHERE table_name = ? AND table_schema = current_schema();, args: [users] +} + +func ExampleDropTable() { + sql, _, err := bob.DropTable("users").Cascade().ToSql() + if err != nil { + log.Fatal(err) + } + + fmt.Println(sql) + // Output: DROP TABLE users CASCADE; +} + +func ExampleDropTableIfExists() { + sql, _, err := bob.DropTableIfExists("users").ToSql() + if err != nil { + log.Fatal(err) + } + + fmt.Println(sql) + // Output: DROP TABLE IF EXISTS users; +} + +func ExampleTruncate() { + sql, _, err := bob.Truncate("users").ToSql() + if err != nil { + log.Fatal(err) + } + + fmt.Println(sql) + // Output: TRUNCATE TABLE users; +} + +func ExampleRenameTable() { + sql, _, err := bob.RenameTable("users", "people").ToSql() + if err != nil { + log.Fatal(err) + } + + fmt.Println(sql) + // Output: ALTER TABLE users RENAME TO people; +} + +func ExampleUpsert() { + // Example for MYSQL + mysql, myArgs, err := bob. + // Notice that you should give database dialect on the second params. + // Available database dialect are MySQL, PostgreSQL, SQLite, and MSSQL. + Upsert("users", bob.MySQL). + Columns("name", "email", "age"). + // You could do multiple Values() call, but I'd suggest to not do it. + // Because this is an upsert function, not an insert one. + Values("Thomas Mueler", "tmueler@something.com", 25). + Replace("age", 25). + ToSql() + if err != nil { + fmt.Printf("Handle this error: %v", err) + } + + // Another example for PostgreSQL + pgsql, pgArgs, err := bob. + Upsert("users", bob.PostgreSQL). + Columns("name", "email", "age"). + Values("Billy Urtha", "billu@something.com", 30). + Key("email"). + Replace("age", 40). + ToSql() + if err != nil { + fmt.Printf("Handle this error: %v", err) + } + + // One more time, for MSSQL / SQL Server. + mssql, msArgs, err := bob. + Upsert("users", bob.MSSQL). + Columns("name", "email", "age"). + Values("George Rust", "georgee@something.com", 19). + Key("email", "georgee@something.com"). + Replace("age", 18). + ToSql() + if err != nil { + fmt.Printf("Handle this error: %v", err) + } + + fmt.Printf("MySQL: %s, %v\n", mysql, myArgs) + fmt.Printf("PostgreSQL: %s, %v\n", pgsql, pgArgs) + fmt.Printf("MSSQL: %s, %v\n", mssql, msArgs) + // Output: + // MySQL: INSERT INTO "users" ("name", "email", "age") VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE "age" = ?;, [Thomas Mueler tmueler@something.com 25 25] + // PostgreSQL: INSERT INTO "users" ("name", "email", "age") VALUES ($1, $2, $3) ON CONFLICT ("email") DO UPDATE SET "age" = $4;, [Billy Urtha billu@something.com 30 40] + // MSSQL: IF NOT EXISTS (SELECT * FROM "users" WHERE "email" = @p1) INSERT INTO "users" ("name", "email", "age") VALUES (@p2, @p3, @p4) ELSE UPDATE "users" SET "age" = @p5 WHERE "email" = @p6;, [georgee@something.com George Rust georgee@something.com 19 18 georgee@something.com] +} From 29f429eda3911d96d4545ae82a3cf05f271b4573 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Fri, 10 Dec 2021 16:50:35 +0700 Subject: [PATCH 2/3] ci: simplifying jobs --- .github/workflows/build.yml | 26 -------------------------- .github/workflows/coverage.yml | 11 +++++++---- 2 files changed, 7 insertions(+), 30 deletions(-) delete mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index f6984cd..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Build test - -on: [pull_request, push] - -jobs: - build-test: - name: Build test - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - go-version: [1.15.x, 1.16.x, 1.17.x] - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Install Go - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go-version }} - - - name: Run go vet - run: go vet - - - name: Test - run: go test -v -race -covermode=atomic -failfast \ No newline at end of file diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index cd0b9d6..c737f07 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -3,21 +3,24 @@ name: Test and coverage on: [push, pull_request] jobs: - build: + check: + name: Check runs-on: ubuntu-latest + needs: build steps: - name: Checkout code uses: actions/checkout@v2 - with: - fetch-depth: 2 - name: Install Go uses: actions/setup-go@v2 with: go-version: 1.17.x + - name: Vet check + run: go vet -v + - name: Run coverage run: go test -v -race -coverprofile=coverage.out -covermode=atomic -failfast - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 \ No newline at end of file + uses: codecov/codecov-action@v1 From 10bad3452b57e5f87c44fe8b9ade1d17a2fec314 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Fri, 10 Dec 2021 16:54:49 +0700 Subject: [PATCH 3/3] docs: repair examples --- example_test.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/example_test.go b/example_test.go index a62a31d..03f17a1 100644 --- a/example_test.go +++ b/example_test.go @@ -2,7 +2,6 @@ package bob_test import ( "fmt" - "log" "github.com/aldy505/bob" ) @@ -33,53 +32,53 @@ func ExampleHasTable() { } func ExampleHasColumn() { - sql, args, err := bob.HasColumn("email").ToSql() + sql, args, err := bob.HasColumn("email").HasTable("users").ToSql() if err != nil { fmt.Printf("Handle this error: %v", err) } fmt.Printf("sql: %s, args: %v", sql, args) - // Output: sql: SELECT * FROM information_schema.columns WHERE table_name = ? AND table_schema = current_schema();, args: [users] + // Output: sql: SELECT * FROM information_schema.columns WHERE table_name = ? AND column_name = ? AND table_schema = current_schema();, args: [users email] } func ExampleDropTable() { sql, _, err := bob.DropTable("users").Cascade().ToSql() if err != nil { - log.Fatal(err) + fmt.Printf("Handle this error: %v", err) } fmt.Println(sql) - // Output: DROP TABLE users CASCADE; + // Output: DROP TABLE "users" CASCADE; } func ExampleDropTableIfExists() { sql, _, err := bob.DropTableIfExists("users").ToSql() if err != nil { - log.Fatal(err) + fmt.Printf("Handle this error: %v", err) } fmt.Println(sql) - // Output: DROP TABLE IF EXISTS users; + // Output: DROP TABLE IF EXISTS "users"; } func ExampleTruncate() { sql, _, err := bob.Truncate("users").ToSql() if err != nil { - log.Fatal(err) + fmt.Printf("Handle this error: %v", err) } fmt.Println(sql) - // Output: TRUNCATE TABLE users; + // Output: TRUNCATE "users"; } func ExampleRenameTable() { sql, _, err := bob.RenameTable("users", "people").ToSql() if err != nil { - log.Fatal(err) + fmt.Printf("Handle this error: %v", err) } fmt.Println(sql) - // Output: ALTER TABLE users RENAME TO people; + // Output: RENAME TABLE "users" TO "people"; } func ExampleUpsert() {