Skip to content

Commit 0a8dbef

Browse files
author
Reinaldy Rafli
authored
Merge pull request #2 from aldy505/feat/definition
feat: using a column definition builder
2 parents 86eb9e0 + 4940a22 commit 0a8dbef

File tree

6 files changed

+346
-102
lines changed

6 files changed

+346
-102
lines changed

README.md

+137-27
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,113 @@
22

33
[![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)
44

5-
I really need a create table SQL builder, and I can't find one. So, like everything else, I made one. Heavily inspired by [Squirrel](https://github.com/Masterminds/squirrel) and [Knex](https://knexjs.org/). And of course, I still use Squirrel for other types of queries (insert, select, and all), think this package as an extension for Squirrel.
5+
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.
66

7-
Oh, and of course, heavily inspired by Bob the Builder.
7+
Oh, and of course, heavily inspired by [Bob the Builder](https://en.wikipedia.org/wiki/Bob_the_Builder).
88

99
```go
1010
import "github.com/aldy505/bob"
1111
```
1212

1313
## Usage
1414

15-
It's not ready for production yet. But, the API is probably close to how you'd do things on Squirrel. This is an example for using with pgx.
15+
It's not ready for large-scale production yet (I've already using it on one of my projects). But, the API is probably close to how you'd do things on Squirrel.
16+
17+
### Create a table
18+
19+
```go
20+
import "github.com/aldy505/bob"
21+
22+
func main() {
23+
// Note that CREATE TABLE don't return args params.
24+
sql, _, err := bob.
25+
CreateTable("tableName").
26+
// The first parameter is the column's name.
27+
// The second parameters and so on forth are extras.
28+
StringColumn("id", "NOT NULL", "PRIMARY KEY", "AUTOINCREMENT").
29+
StringColumn("email", "NOT NULL", "UNIQUE").
30+
// See the list of available column definition type through pkg.go.dev or scroll down below.
31+
TextColumn("password").
32+
// Or add your custom types
33+
AddColumn(bob.ColumnDef{Name: "tableName", Type: "customType", Extras: []string{"NOT NULL"}}).
34+
ToSql()
35+
if err != nil {
36+
// handle your error
37+
}
38+
}
39+
```
40+
41+
Available column definition types:
42+
* `StringColumn()` - Default to `VARCHAR(255)`
43+
* `TextColumn()` - Default to `TEXT`
44+
* `UUIDColumn()` - Defaults to `UUID`
45+
* `BooleanColumn()` - Defaults to `BOOLEAN`
46+
* `IntegerColumn()` - Defaults to `INTEGER`. Postgres and SQLite only.
47+
* `IntColumn()` - Defaults to `INT`. MySQL and MSSQL only.
48+
* `RealColumn()` - Defaults to `REAL`. Postgres, MSSQL, and SQLite only.
49+
* `FloatColumn()` - Defaults to `FLOAT`. Postgres and SQLite only.
50+
* `DateTimeColumn()` - Defaults to `DATETIME`.
51+
* `TimeStampColumn()` - Defaults to `TIMESTAMP`.
52+
* `TimeColumn()` - Defaults to `TIME`.
53+
* `DateColumn()` - Defaults to `DATE`.
54+
* `JSONColumn()` - Dafults to `JSON`. MySQL and Postgres only.
55+
* `JSONBColumn()` - Defaults to `JSONB`. Postgres only.
56+
* `BlobColumn()` - Defaults to `BLOB`. MySQL and SQLite only.
57+
58+
For any other types, please use `AddColumn()`.
59+
60+
Another builder of `bob.CreateTableIfNotExists()` is also available.
61+
62+
### Check if a table exists
63+
64+
```go
65+
func main() {
66+
sql, args, err := bob.HasTable("users").ToSql()
67+
if err != nil {
68+
log.Fatal(err)
69+
}
70+
}
71+
```
72+
73+
### Check if a column exists
74+
75+
```go
76+
func main() {
77+
sql, args, err := bob.HasColumn("email").ToSql()
78+
if err != nil {
79+
log.Fatal(err)
80+
}
81+
}
82+
```
83+
84+
### Placeholder format
85+
86+
Default placeholder is a question mark (MySQL-like). If you want to change it, simply use something like this:
87+
88+
```go
89+
func main() {
90+
// Option 1
91+
sql, args, err := bob.HasTable("users").PlaceholderFormat(bob.Dollar).ToSql()
92+
if err != nil {
93+
log.Fatal(err)
94+
}
95+
96+
// Option 2
97+
sql, args, err = bob.HasTable("users").ToSql()
98+
if err != nil {
99+
log.Fatal(err)
100+
}
101+
correctPlaceholder := bob.ReplacePlaceholder(sql, bob.Dollar)
102+
}
103+
```
104+
105+
Available placeholder formats:
106+
* `bob.Question` - `INSERT INTO "users" (name) VALUES (?)`
107+
* `bob.Dollar` - `INSERT INTO "users" (name) VALUES ($1)`
108+
* `bob.Colon` - `INSERT INTO "users" (name) VALUES (:1)`
109+
* `bob.AtP` - `INSERT INTO "users" (name) VALUES (@p1)`
110+
111+
### With pgx (PostgreSQL)
16112

17113
```go
18114
import (
@@ -36,7 +132,7 @@ func main() {
36132
var hasTableUsers bool
37133
err = db.QueryRow(context.Background(), sql, args...).Scan(&hasTableUsers)
38134
if err != nil {
39-
if err.Error() == "no rows in result set" {
135+
if err == bob.ErrEmptyTablePg {
40136
hasTableUsers = false
41137
} else {
42138
log.Fatal(err)
@@ -46,47 +142,61 @@ func main() {
46142
if !hasTableUsers {
47143
// Create "users" table
48144
// Note that this will return multiple query in a single string.
49-
sql, _, err := bob.CreateTable("users").
50-
Columns("id", "email", "name", "password", "date").
51-
Types("varchar(36)", "varchar(255)", "varchar(255)", "text", "date").
52-
Primary("id").
53-
Unique("email")
145+
sql, _, err := bob.
146+
CreateTable("users").
147+
IntegerColumn("id", "PRIMARY KEY", "SERIAL").
148+
StringColumn("name", "NOT NULL").
149+
TextColumn("password", "NOT NULL").
150+
DateColumn("created_at").
54151
ToSql()
55152
if err != nil {
56153
log.Fatal(err)
57154
}
58155

59-
// If you don't do this, you will get the error:
60-
// ERROR: cannot insert multiple commands into a prepared statement (SQLSTATE 42601)
61-
splitQuery := strings.Split(sql, ";")
62-
for i := range splitQuery {
63-
_, err = db.Query(context.Background(), splitQuery[i])
64-
if err != nil {
65-
log.Fatal(err)
66-
}
156+
_, err = db.Query(context.Background(), splitQuery[i])
157+
if err != nil {
158+
log.Fatal(err)
67159
}
68160

69161
// Create another table, this time with CREATE TABLE IF NOT EXISTS
70-
sql, _, err := bob.CreateTableIfNotExists("inventory").
71-
Columns("id", "userID", "items", "quantity").
72-
Types("varchar(36)", "varchar(36)", "json", "int").
73-
Primary("id").
162+
sql, _, err := bob.
163+
CreateTableIfNotExists("inventory").
164+
UUIDColumn("id", "PRIMARY KEY").
165+
IntegerColumn("userID", "FOREIGN KEY REFERENCES users(id)").
166+
JSONColumn("items").
167+
IntegerColumn("quantity").
74168
ToSql()
75169
if err != nil {
76170
log.Fatal(err)
77171
}
78172

79-
inventoryQuery := strings.Split(sql, ";")
80-
for i := range inventoryQuery {
81-
_, err = db.Query(context.Background(), inventoryQuery[i])
82-
if err != nil {
83-
log.Fatal(err)
84-
}
173+
_, err = db.Query(context.Background(), inventoryQuery[i])
174+
if err != nil {
175+
log.Fatal(err)
85176
}
86177
}
87178
}
88179
```
89180

181+
## Features
182+
183+
* `bob.CreateTable(tableName)` - Basic SQL create table
184+
* `bob.CreateTableIfNotExists(tableName)` - Create table if not exists
185+
* `bob.HasTable(tableName)` - Checks if column exists (return error if false, check example above for error handling)
186+
* `bob.HasColumn(columnName)` - Check if a column exists on current table
187+
188+
### TODO
189+
190+
Meaning these are some ideas for the future development of Bob.
191+
192+
* `bob.DropTable(tableName)` - Drop a table (`drop table "users"`)
193+
* `bob.DropTableIfExists(tableName)` - Drop a table if exists (`drop table if exists "users"`)
194+
* `bob.RenameTable(tableName)` - Rename a table (`rename table "users" to "old_users"`)
195+
* `bob.Truncate(tableName)` - Truncate a table (`truncate "users"`)
196+
* `bob.Upsert(tableName)` - UPSERT function (`insert into "users" ("name", "email") values (?, ?) on duplicate key update email = ?`)
197+
* `bob.ExecWith()` - Just like Squirrel's [ExecWith](https://pkg.go.dev/github.com/Masterminds/squirrel?utm_source=godoc#ExecWith)
198+
* `bob.Count(tableName, columnName)` - Count query (`select count("active") from "users"`)
199+
90200
## Contributing
91201

92202
Contributions are always welcome! As long as you add a test for your changes.

bob.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
package bob
22

3-
import "github.com/lann/builder"
3+
import (
4+
"errors"
5+
6+
"github.com/lann/builder"
7+
)
8+
9+
var ErrEmptyTable = errors.New("sql: no rows in result set")
10+
var ErrEmptyTablePgx = errors.New("no rows in result set")
411

512
// BobBuilderType is the type for BobBuilder
613
type BobBuilderType builder.Builder

0 commit comments

Comments
 (0)