Skip to content

Instantly share code, notes, and snippets.

@aldy505
Last active December 1, 2021 10:12
Show Gist options
  • Save aldy505/69ac01adb11121c9747632839d1a0851 to your computer and use it in GitHub Desktop.
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
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