From 070280bdf7fb5d08f658d0dedf68ccb6744e5256 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Tue, 9 Nov 2021 12:16:10 +0700 Subject: [PATCH 1/3] feat: create index method --- bob.go | 20 ++++ create_index.go | 127 +++++++++++++++++++++++++ create_index_test.go | 91 ++++++++++++++++++ create.go => create_table.go | 0 create_test.go => create_table_test.go | 0 5 files changed, 238 insertions(+) create mode 100644 create_index.go create mode 100644 create_index_test.go rename create.go => create_table.go (100%) rename create_test.go => create_table_test.go (100%) diff --git a/bob.go b/bob.go index a8a4470..001424d 100644 --- a/bob.go +++ b/bob.go @@ -40,6 +40,16 @@ func (b BobBuilderType) CreateTableIfNotExists(table string) CreateBuilder { 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 func (b BobBuilderType) HasTable(table string) HasBuilder { return HasBuilder(b).HasTable(table) @@ -172,3 +182,13 @@ func Truncate(table string) TruncateBuilder { func Upsert(table string, dialect int) UpsertBuilder { 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) +} \ No newline at end of file diff --git a/create_index.go b/create_index.go new file mode 100644 index 0000000..ab008b3 --- /dev/null +++ b/create_index.go @@ -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 +} \ No newline at end of file diff --git a/create_index_test.go b/create_index_test.go new file mode 100644 index 0000000..daa499b --- /dev/null +++ b/create_index_test.go @@ -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()) + } + }) +} \ No newline at end of file diff --git a/create.go b/create_table.go similarity index 100% rename from create.go rename to create_table.go diff --git a/create_test.go b/create_table_test.go similarity index 100% rename from create_test.go rename to create_table_test.go From a891b58ffb309aa2c9026f143a71401c63917aef Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Tue, 9 Nov 2021 12:16:31 +0700 Subject: [PATCH 2/3] chore: fmt --- bob.go | 2 +- create_index.go | 22 +++++++++++----------- create_index_test.go | 4 ++-- upsert_test.go | 8 ++++---- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/bob.go b/bob.go index 001424d..18d48d2 100644 --- a/bob.go +++ b/bob.go @@ -191,4 +191,4 @@ func CreateIndex(name string) IndexBuilder { // CreateIndexIfNotExists creates an index with CreateIndexBuilder interface, if the index doesn't exists. func CreateIndexIfNotExists(name string) IndexBuilder { return BobStmtBuilder.CreateIndexIfNotExists(name) -} \ No newline at end of file +} diff --git a/create_index.go b/create_index.go index ab008b3..3885361 100644 --- a/create_index.go +++ b/create_index.go @@ -10,18 +10,18 @@ import ( type IndexBuilder builder.Builder type indexData struct { - Unique bool - Spatial bool - Fulltext bool - Name string - TableName string - Columns []IndexColumn + Unique bool + Spatial bool + Fulltext bool + Name string + TableName string + Columns []IndexColumn IfNotExists bool } type IndexColumn struct { - Name string - Extras []string + Name string + Extras []string Collate string } @@ -76,7 +76,7 @@ func (i *indexData) ToSql() (sqlStr string, args []interface{}, err error) { err = errors.New("should at least specify one column for create index statement") return } - + var sql strings.Builder sql.WriteString("CREATE ") @@ -121,7 +121,7 @@ func (i *indexData) ToSql() (sqlStr string, args []interface{}, err error) { sql.WriteString("(") sql.WriteString(strings.Join(columns, ", ")) sql.WriteString(");") - + sqlStr = sql.String() return -} \ No newline at end of file +} diff --git a/create_index_test.go b/create_index_test.go index daa499b..ef32966 100644 --- a/create_index_test.go +++ b/create_index_test.go @@ -58,7 +58,7 @@ func TestCreateIndex_Error(t *testing.T) { t.Fatal("error is not equal to result:", err.Error()) } }) - + t.Run("without table name", func(t *testing.T) { _, _, err := bob. CreateIndex("name"). @@ -88,4 +88,4 @@ func TestCreateIndex_Error(t *testing.T) { t.Fatal("error is not equal to result:", err.Error()) } }) -} \ No newline at end of file +} diff --git a/upsert_test.go b/upsert_test.go index 317ae8a..3dcb52d 100644 --- a/upsert_test.go +++ b/upsert_test.go @@ -189,10 +189,10 @@ func TestUpsert_WithoutReplacePlaceHolder(t *testing.T) { if err != nil { 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;" desiredArgs := []interface{}{"john@doe.com", "John Doe", "john@doe.com", "John Does", "john@doe.com"} - + if sql != desiredSql { 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.Run("SQLite", func(t *testing.T) { sql, args, err := bob. Upsert("users", bob.SQLite). @@ -223,4 +223,4 @@ func TestUpsert_WithoutReplacePlaceHolder(t *testing.T) { t.Error("args is not the same as result: ", args) } }) -} \ No newline at end of file +} From 5af711e253420ab7c91d7ba3c01088dc603abd91 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Tue, 9 Nov 2021 12:23:45 +0700 Subject: [PATCH 3/3] refactor: clean up test create table --- create_table_test.go | 142 +++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/create_table_test.go b/create_table_test.go index 3c92425..0942e90 100644 --- a/create_table_test.go +++ b/create_table_test.go @@ -6,67 +6,67 @@ import ( "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) - } - }) +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) + } +} - 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() +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) - } - }) + 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) - } - }) +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(""). @@ -85,18 +85,18 @@ func TestCreate(t *testing.T) { 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) - } - }) +} + +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) + } }