Merge branch 'master' of github.com:aldy505/bob

This commit is contained in:
Reinaldy Rafli 2021-11-09 12:30:12 +07:00
commit 990f053673
7 changed files with 344 additions and 106 deletions

20
bob.go
View File

@ -40,6 +40,16 @@ func (b BobBuilderType) CreateTableIfNotExists(table string) CreateBuilder {
return CreateBuilder(b).name(table).ifNotExists() return CreateBuilder(b).name(table).ifNotExists()
} }
// CreateIndex creates an index with CreateIndexBuilder interface.
func (b BobBuilderType) CreateIndex(name string) IndexBuilder {
return IndexBuilder(b).name(name)
}
// CreateIndexIfNotExists creates an index with CreateIndexBuilder interface, if the index doesn't exists.
func (b BobBuilderType) CreateIndexIfNotExists(name string) IndexBuilder {
return IndexBuilder(b).name(name).ifNotExists()
}
// HasTable checks if a table exists with HasBuilder interface // HasTable checks if a table exists with HasBuilder interface
func (b BobBuilderType) HasTable(table string) HasBuilder { func (b BobBuilderType) HasTable(table string) HasBuilder {
return HasBuilder(b).HasTable(table) return HasBuilder(b).HasTable(table)
@ -172,3 +182,13 @@ func Truncate(table string) TruncateBuilder {
func Upsert(table string, dialect int) UpsertBuilder { func Upsert(table string, dialect int) UpsertBuilder {
return BobStmtBuilder.Upsert(table, dialect) return BobStmtBuilder.Upsert(table, dialect)
} }
// CreateIndex creates an index with CreateIndexBuilder interface.
func CreateIndex(name string) IndexBuilder {
return BobStmtBuilder.CreateIndex(name)
}
// CreateIndexIfNotExists creates an index with CreateIndexBuilder interface, if the index doesn't exists.
func CreateIndexIfNotExists(name string) IndexBuilder {
return BobStmtBuilder.CreateIndexIfNotExists(name)
}

127
create_index.go Normal file
View File

@ -0,0 +1,127 @@
package bob
import (
"errors"
"strings"
"github.com/lann/builder"
)
type IndexBuilder builder.Builder
type indexData struct {
Unique bool
Spatial bool
Fulltext bool
Name string
TableName string
Columns []IndexColumn
IfNotExists bool
}
type IndexColumn struct {
Name string
Extras []string
Collate string
}
func init() {
builder.Register(IndexBuilder{}, indexData{})
}
func (i IndexBuilder) Unique() IndexBuilder {
return builder.Set(i, "Unique", true).(IndexBuilder)
}
func (i IndexBuilder) Spatial() IndexBuilder {
return builder.Set(i, "Spatial", true).(IndexBuilder)
}
func (i IndexBuilder) Fulltext() IndexBuilder {
return builder.Set(i, "Fulltext", true).(IndexBuilder)
}
func (i IndexBuilder) name(name string) IndexBuilder {
return builder.Set(i, "Name", name).(IndexBuilder)
}
func (i IndexBuilder) ifNotExists() IndexBuilder {
return builder.Set(i, "IfNotExists", true).(IndexBuilder)
}
func (i IndexBuilder) On(table string) IndexBuilder {
return builder.Set(i, "TableName", table).(IndexBuilder)
}
func (i IndexBuilder) Columns(column IndexColumn) IndexBuilder {
return builder.Append(i, "Columns", column).(IndexBuilder)
}
func (i IndexBuilder) ToSql() (string, []interface{}, error) {
data := builder.GetStruct(i).(indexData)
return data.ToSql()
}
func (i *indexData) ToSql() (sqlStr string, args []interface{}, err error) {
if i.Name == "" {
err = errors.New("index name is required on create index statement")
return
}
if i.TableName == "" {
err = errors.New("a table name must be specified on create index statement")
return
}
if len(i.Columns) == 0 {
err = errors.New("should at least specify one column for create index statement")
return
}
var sql strings.Builder
sql.WriteString("CREATE ")
if i.Unique {
sql.WriteString("UNIQUE ")
}
if i.Fulltext {
sql.WriteString("FULLTEXT ")
}
if i.Spatial {
sql.WriteString("SPATIAL ")
}
sql.WriteString("INDEX ")
if i.IfNotExists {
sql.WriteString("IF NOT EXISTS ")
}
sql.WriteString(i.Name + " ")
sql.WriteString("ON ")
sql.WriteString(i.TableName + " ")
var columns []string
for _, column := range i.Columns {
var colBuilder strings.Builder
colBuilder.WriteString(column.Name)
if column.Collate != "" {
colBuilder.WriteString(" COLLATE " + column.Collate)
}
if len(column.Extras) > 0 {
colBuilder.WriteString(" " + strings.Join(column.Extras, " "))
}
columns = append(columns, colBuilder.String())
}
sql.WriteString("(")
sql.WriteString(strings.Join(columns, ", "))
sql.WriteString(");")
sqlStr = sql.String()
return
}

91
create_index_test.go Normal file
View File

@ -0,0 +1,91 @@
package bob_test
import (
"testing"
"github.com/aldy505/bob"
)
func TestCreateIndex(t *testing.T) {
sql, _, err := bob.
CreateIndexIfNotExists("email_idx").
On("users").
Unique().
Spatial().
Fulltext().
Columns(bob.IndexColumn{Name: "email"}).
ToSql()
if err != nil {
t.Fatal(err.Error())
}
result := "CREATE UNIQUE FULLTEXT SPATIAL INDEX IF NOT EXISTS email_idx ON users (email);"
if sql != result {
t.Fatal("sql is not equal to result:", sql)
}
}
func TestCreateIndex_Simple(t *testing.T) {
sql, _, err := bob.
CreateIndex("idx_email").
On("users").
Columns(bob.IndexColumn{Name: "email", Collate: "DEFAULT", Extras: []string{"ASC"}}).
Columns(bob.IndexColumn{Name: "name", Extras: []string{"DESC"}}).
ToSql()
if err != nil {
t.Fatal(err.Error())
}
result := "CREATE INDEX idx_email ON users (email COLLATE DEFAULT ASC, name DESC);"
if sql != result {
t.Fatal("sql is not equal to result:", sql)
}
}
func TestCreateIndex_Error(t *testing.T) {
t.Run("without index", func(t *testing.T) {
_, _, err := bob.
CreateIndex("").
On("users").
Columns(bob.IndexColumn{Name: "email", Collate: "DEFAULT", Extras: []string{"ASC"}}).
Columns(bob.IndexColumn{Name: "name", Extras: []string{"DESC"}}).
ToSql()
if err == nil {
t.Fatal("error is nil")
}
if err.Error() != "index name is required on create index statement" {
t.Fatal("error is not equal to result:", err.Error())
}
})
t.Run("without table name", func(t *testing.T) {
_, _, err := bob.
CreateIndex("name").
On("").
Columns(bob.IndexColumn{Name: "email", Collate: "DEFAULT", Extras: []string{"ASC"}}).
Columns(bob.IndexColumn{Name: "name", Extras: []string{"DESC"}}).
ToSql()
if err == nil {
t.Fatal("error is nil")
}
if err.Error() != "a table name must be specified on create index statement" {
t.Fatal("error is not equal to result:", err.Error())
}
})
t.Run("without columns", func(t *testing.T) {
_, _, err := bob.
CreateIndex("name").
On("users").
ToSql()
if err == nil {
t.Fatal("error is nil")
}
if err.Error() != "should at least specify one column for create index statement" {
t.Fatal("error is not equal to result:", err.Error())
}
})
}

102
create_table_test.go Normal file
View File

@ -0,0 +1,102 @@
package bob_test
import (
"testing"
"github.com/aldy505/bob"
)
func TestCreateTable(t *testing.T) {
sql, _, err := bob.
CreateTable("users").
UUIDColumn("uuid").
StringColumn("string").
TextColumn("text").
DateColumn("date").
BooleanColumn("boolean").
IntegerColumn("integer").
IntColumn("int").
TimeStampColumn("timestamp").
TimeColumn("time").
DateColumn("date").
DateTimeColumn("datetime").
JSONColumn("json").
JSONBColumn("jsonb").
BlobColumn("blob").
RealColumn("real").
FloatColumn("float").
AddColumn(bob.ColumnDef{Name: "custom", Type: "custom"}).
ToSql()
if err != nil {
t.Fatal(err.Error())
}
result := "CREATE TABLE \"users\" (\"uuid\" UUID, \"string\" VARCHAR(255), \"text\" TEXT, \"date\" DATE, \"boolean\" BOOLEAN, \"integer\" INTEGER, \"int\" INT, \"timestamp\" TIMESTAMP, \"time\" TIME, \"date\" DATE, \"datetime\" DATETIME, \"json\" JSON, \"jsonb\" JSONB, \"blob\" BLOB, \"real\" REAL, \"float\" FLOAT, \"custom\" custom);"
if sql != result {
t.Fatal("sql is not equal to result:", sql)
}
}
func TestCreateTable_Extras(t *testing.T) {
sql, _, err := bob.CreateTable("users").
UUIDColumn("id", "PRIMARY KEY").
StringColumn("email", "NOT NULL", "UNIQUE").
ToSql()
if err != nil {
t.Fatal(err.Error())
}
result := "CREATE TABLE \"users\" (\"id\" UUID PRIMARY KEY, \"email\" VARCHAR(255) NOT NULL UNIQUE);"
if sql != result {
t.Fatal("sql is not equal to result:", sql)
}
}
func TestCreateTable_Schema(t *testing.T) {
sql, _, err := bob.
CreateTable("users").
WithSchema("private").
StringColumn("name").
ToSql()
if err != nil {
t.Fatal(err.Error())
}
result := "CREATE TABLE \"private\".\"users\" (\"name\" VARCHAR(255));"
if sql != result {
t.Fatal("sql is not equal to result:", sql)
}
}
func TestCreateTable_Error(t *testing.T) {
t.Run("should emit error on empty table name", func(t *testing.T) {
_, _, err := bob.
CreateTable("").
StringColumn("name").
ToSql()
if err.Error() != "create statements must specify a table" {
t.Fatal("should throw an error, it didn't:", err.Error())
}
})
t.Run("should emit error if no column were specified", func(t *testing.T) {
_, _, err := bob.
CreateTable("users").
ToSql()
if err.Error() != "a table should at least have one column" {
t.Fatal("should throw an error, it didn't:", err.Error())
}
})
}
func TestCreateTable_IfNotExists(t *testing.T) {
sql, _, err := bob.
CreateTableIfNotExists("users").
TextColumn("name").
ToSql()
if err != nil {
t.Fatal(err.Error())
}
result := "CREATE TABLE IF NOT EXISTS \"users\" (\"name\" TEXT);"
if sql != result {
t.Fatal("sql is not equal to result: ", sql)
}
}

View File

@ -1,102 +0,0 @@
package bob_test
import (
"testing"
"github.com/aldy505/bob"
)
func TestCreate(t *testing.T) {
t.Run("should return correct sql string with all columns and types", func(t *testing.T) {
sql, _, err := bob.
CreateTable("users").
UUIDColumn("uuid").
StringColumn("string").
TextColumn("text").
DateColumn("date").
BooleanColumn("boolean").
IntegerColumn("integer").
IntColumn("int").
TimeStampColumn("timestamp").
TimeColumn("time").
DateColumn("date").
DateTimeColumn("datetime").
JSONColumn("json").
JSONBColumn("jsonb").
BlobColumn("blob").
RealColumn("real").
FloatColumn("float").
AddColumn(bob.ColumnDef{Name: "custom", Type: "custom"}).
ToSql()
if err != nil {
t.Fatal(err.Error())
}
result := "CREATE TABLE \"users\" (\"uuid\" UUID, \"string\" VARCHAR(255), \"text\" TEXT, \"date\" DATE, \"boolean\" BOOLEAN, \"integer\" INTEGER, \"int\" INT, \"timestamp\" TIMESTAMP, \"time\" TIME, \"date\" DATE, \"datetime\" DATETIME, \"json\" JSON, \"jsonb\" JSONB, \"blob\" BLOB, \"real\" REAL, \"float\" FLOAT, \"custom\" custom);"
if sql != result {
t.Fatal("sql is not equal to result:", sql)
}
})
t.Run("should return correct sql with extras", func(t *testing.T) {
sql, _, err := bob.CreateTable("users").
UUIDColumn("id", "PRIMARY KEY").
StringColumn("email", "NOT NULL", "UNIQUE").
ToSql()
if err != nil {
t.Fatal(err.Error())
}
result := "CREATE TABLE \"users\" (\"id\" UUID PRIMARY KEY, \"email\" VARCHAR(255) NOT NULL UNIQUE);"
if sql != result {
t.Fatal("sql is not equal to result:", sql)
}
})
t.Run("should be able to have a schema name", func(t *testing.T) {
sql, _, err := bob.
CreateTable("users").
WithSchema("private").
StringColumn("name").
ToSql()
if err != nil {
t.Fatal(err.Error())
}
result := "CREATE TABLE \"private\".\"users\" (\"name\" VARCHAR(255));"
if sql != result {
t.Fatal("sql is not equal to result:", sql)
}
})
t.Run("should emit error on empty table name", func(t *testing.T) {
_, _, err := bob.
CreateTable("").
StringColumn("name").
ToSql()
if err.Error() != "create statements must specify a table" {
t.Fatal("should throw an error, it didn't:", err.Error())
}
})
t.Run("should emit error if no column were specified", func(t *testing.T) {
_, _, err := bob.
CreateTable("users").
ToSql()
if err.Error() != "a table should at least have one column" {
t.Fatal("should throw an error, it didn't:", err.Error())
}
})
t.Run("should emit create if not exists", func(t *testing.T) {
sql, _, err := bob.
CreateTableIfNotExists("users").
TextColumn("name").
ToSql()
if err != nil {
t.Fatal(err.Error())
}
result := "CREATE TABLE IF NOT EXISTS \"users\" (\"name\" TEXT);"
if sql != result {
t.Fatal("sql is not equal to result: ", sql)
}
})
}

View File

@ -189,10 +189,10 @@ func TestUpsert_WithoutReplacePlaceHolder(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
desiredSql := "IF NOT EXISTS (SELECT * FROM \"users\" WHERE \"email\" = @p1) INSERT INTO \"users\" (\"name\", \"email\") VALUES (@p2, @p3) ELSE UPDATE \"users\" SET \"name\" = @p4 WHERE \"email\" = @p5;" desiredSql := "IF NOT EXISTS (SELECT * FROM \"users\" WHERE \"email\" = @p1) INSERT INTO \"users\" (\"name\", \"email\") VALUES (@p2, @p3) ELSE UPDATE \"users\" SET \"name\" = @p4 WHERE \"email\" = @p5;"
desiredArgs := []interface{}{"john@doe.com", "John Doe", "john@doe.com", "John Does", "john@doe.com"} desiredArgs := []interface{}{"john@doe.com", "John Doe", "john@doe.com", "John Does", "john@doe.com"}
if sql != desiredSql { if sql != desiredSql {
t.Error("sql is not the same as result: ", sql) t.Error("sql is not the same as result: ", sql)
} }
@ -200,7 +200,7 @@ func TestUpsert_WithoutReplacePlaceHolder(t *testing.T) {
t.Error("args is not the same as result: ", args) t.Error("args is not the same as result: ", args)
} }
}) })
t.Run("SQLite", func(t *testing.T) { t.Run("SQLite", func(t *testing.T) {
sql, args, err := bob. sql, args, err := bob.
Upsert("users", bob.SQLite). Upsert("users", bob.SQLite).
@ -223,4 +223,4 @@ func TestUpsert_WithoutReplacePlaceHolder(t *testing.T) {
t.Error("args is not the same as result: ", args) t.Error("args is not the same as result: ", args)
} }
}) })
} }