Last active
December 1, 2021 10:12
-
-
Save aldy505/69ac01adb11121c9747632839d1a0851 to your computer and use it in GitHub Desktop.
Simple exercise for learning strings standard library and basic logic of builder query
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"strings" | |
) | |
// Hello again, soldier. | |
// | |
// In today's episode, we are going to make a simple | |
// SQL Query Builder library, from zero. | |
// | |
// So we will only make INSERT and SELECT statement. | |
// The input is a struct called Builder, which | |
// I'll make it first for you, then you'll create | |
// the function for it. | |
// | |
// For creating the string, we can use two things: | |
// 1. Normal contatenation with | |
// var s string | |
// s += "blah" | |
// 2. strings.Builder: | |
// var s strings.Builder | |
// s.WriteString("blah") | |
// fmt.Println(s.String()) | |
// | |
// It is up to you about which one to use, | |
// but I should tell you that using strings.Builder | |
// is significantly faster than using normal string | |
// concatenation. | |
type Builder struct { | |
TableName string | |
Columns []Column | |
Rows [][]Row | |
Condition Condition | |
} | |
type Column struct { | |
Name string | |
DataType string | |
} | |
type Row struct { | |
ColumnName string | |
Value string | |
} | |
type Condition struct { | |
Where M | |
OrderBy string | |
GroupBy string | |
} | |
type M map[string]string | |
// This is an insert builder, it returns the whole SQL as a string | |
// no need to do prepared statement, because that would be | |
// a bit harder. | |
func InsertBuilder(b Builder) string { | |
var sql strings.Builder | |
sql.WriteString("INSERT INTO ") | |
sql.WriteString(b.TableName) | |
// give the column names here | |
// use a simple for loop. | |
sql.WriteString("VALUES") | |
// give the rows name here, match it with b.Row.ColumnName | |
// don't forget that it could be multiple values | |
sql.WriteString(";") | |
return sql.String() | |
} | |
func SelectBuilder(b Builder) string { | |
var sql strings.Builder | |
sql.WriteString("SELECT ") | |
// do a for loop for the column names | |
// if the length of b.Columns == 0 | |
// put a *, so it'll be SELECT * | |
sql.WriteString("FROM ") | |
sql.WriteString(b.TableName) | |
// check if the b.Condition.Where is empty or not | |
// if it's not empty, do some logic | |
// how to check if a map is empty or not? use len() | |
// https://stackoverflow.com/questions/12544641/how-to-count-items-in-a-go-map | |
// | |
// here, I'll make it easy for you | |
if len(b.Condition.Where) > 0 { | |
sql.WriteString("WHERE ") | |
for key, value := range b.Condition.Where { | |
sql.WriteString(key) | |
sql.WriteString(" = ") | |
sql.WriteString(value) | |
} | |
// yep, it's that simple! | |
// no magic magic club like PHP | |
} | |
// now do the same thing for the group by, | |
// and order by clauses | |
// then it's done | |
return sql.String() | |
} | |
// Test your code, shall we? | |
type testObj struct { | |
t int8 | |
b Builder | |
e string | |
} | |
func main() { | |
fmt.Print("Hello! I'll test your code.\n\n") | |
tests := []testObj{ | |
{ | |
t: 1, | |
e: "INSERT INTO users (name, age, occupation) VALUES (Dicha Arkana, 18, student), (Jason Wihardja, 29, architect), (Valian Masdani, 22, it staff);", | |
b: Builder{ | |
TableName: "users", | |
Columns: []Column{ | |
{Name: "name", DataType: "string"}, | |
{Name: "age", DataType: "integer"}, | |
{Name: "occupation", DataType: "string"}, | |
}, | |
Rows: [][]Row{ | |
{ | |
{ColumnName: "age", Value: "18"}, // happy birthday | |
{ColumnName: "name", Value: "Dicha Arkana"}, | |
{ColumnName: "occupation", Value: "student"}, | |
}, | |
{ | |
{ColumnName: "name", Value: "Jason Wihardja"}, | |
{ColumnName: "occupation", Value: "architect"}, | |
{ColumnName: "age", Value: "29"}, | |
}, | |
{ | |
{ColumnName: "name", Value: "Valian Masdani"}, | |
{ColumnName: "age", Value: "22"}, | |
{ColumnName: "occupation", Value: "it staff"}, | |
}, | |
}, | |
}, | |
}, | |
{ | |
t: 1, | |
e: "INSERT INTO users (name, age) VALUES (John Doe, 40);", | |
b: Builder{ | |
TableName: "users", | |
Columns: []Column{ | |
{Name: "name", DataType: "string"}, | |
{Name: "age", DataType: "integer"}, | |
}, | |
Rows: [][]Row{ | |
{ | |
{ColumnName: "name", Value: "John Doe"}, | |
{ColumnName: "age", Value: "40"}, | |
}, | |
}, | |
}, | |
}, | |
{ | |
t: 0, | |
e: "SELECT * FROM users;", | |
b: Builder{ | |
TableName: "users", | |
}, | |
}, | |
{ | |
t: 0, | |
e: "SELECT * FROM users WHERE id = 2;", | |
b: Builder{ | |
TableName: "users", | |
Condition: Condition{ | |
Where: M{"id": "2"}, | |
}, | |
}, | |
}, | |
{ | |
t: 0, | |
e: "SELECT name FROM users WHERE id = 1 GROUP BY age ORDER BY birthdate;", | |
b: Builder{ | |
TableName: "users", | |
Columns: []Column{ | |
{Name: "name", DataType: "string"}, | |
}, | |
Condition: Condition{ | |
Where: M{"id": "1"}, | |
GroupBy: "age", | |
OrderBy: "birthdate", | |
}, | |
}, | |
}, | |
} | |
for i, test := range tests { | |
switch test.t { | |
case 1: | |
if r := InsertBuilder(test.b); r != test.e { | |
fmt.Printf("--- Failed on test #%d\nGot: %s\nExpected: %s\n\n", i, r, test.e) | |
} else { | |
fmt.Printf("--- Succeed on test #%d\n", i) | |
} | |
default: | |
if r := SelectBuilder(test.b); r != test.e { | |
fmt.Printf("--- Failed on test #%d\nGot: %s\nExpected: %s\n\n", i, r, test.e) | |
} else { | |
fmt.Printf("--- Succeed on test #%d\n", i) | |
} | |
} | |
} | |
fmt.Println("Test finished! How did it go?") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment