-
Notifications
You must be signed in to change notification settings - Fork 4
Need Code Review #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Conversation
## How to test: | ||
|
||
1. Change into directory of server_test.go | ||
2. In the terminal, type "go test" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried running this and it prints error to the console several times, but than it passes which confused me at first. I thought it didn't work, probably because I don't have the same mysql setup as you. Maybe don't print error? or maybe it is passing but shouldn't be
% go test
Unable to make connection
Error from Database [email protected] is not in the database
Unable to make connection
Error from Database [email protected] is not in the database
PASS
ok github.com/blics18/SendGrid/server 0.117s
Also I would consider writing and committing a createTables.sql file that generates the schema.
|
||
func createTable(numTable int, db *sql.DB) error { | ||
stmt := fmt.Sprintf(`CREATE TABLE IF NOT EXISTS User%02d ( | ||
id int NOT NULL AUTO_INCREMENT, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of creating the tables inside your service, you might want to take advantage of docker which you could configure to spin up a mysql instance and then populate it every time... e.g. https://github.com/jimmyjames85/bpmonitor
|
||
p := MakeRandomUsers(numUsers, numEmails) | ||
|
||
db, err := sql.Open("mysql", "root:SendGrid@tcp(localhost:3306)/UserStructs") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of hardcoding the username and password, consider passing them in as an environment variable that could retrieve via os.Getenv("DB_HOST")
or often we use https://github.com/kelseyhightower/envconfig
} | ||
|
||
func crossCheck(UserID *int, Email string) bool { | ||
db, err := sql.Open("mysql", "root:SendGrid@tcp(localhost:3306)/UserStructs") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here db
is a connection pool. To gain the benefits of having a pool it is best to keep this open all the time...for the lifetime of the app. You could than access it as part of the server. e.g.
func (bf *boomFilter) crossCheck(UserID *int, Email string) bool {
rows, err := bf.db.Query(stmt)
.....
}
db, err := sql.Open("mysql", "root:SendGrid@tcp(localhost:3306)/UserStructs") | ||
if err != nil { | ||
fmt.Println("Failed to get handle") | ||
db.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return from the function
err = db.Ping() | ||
if err != nil { | ||
fmt.Println("Unable to make connection") | ||
db.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return from the function and bubble up the error
} | ||
} | ||
|
||
func crossCheck(UserID *int, Email string) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if UserID
is nil. We should check for that...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would we have to check for this still? Because in check before UserID gets passed to crossCheck, we already checked if it is nil
|
||
const numTables int = 5 | ||
|
||
stmt := fmt.Sprintf("SELECT uid, email FROM User%02d WHERE uid=%d AND email='%s'", (*UserID)%numTables, *UserID, Email) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can get dangerous, trying interpolate parameters with Sprintf. To prevent sql injection you could do something like this
stmt := fmt.Sprintf("SELECT uid, email FROM User02 WHERE uid=? AND email=?")
rows, err := db.Query(stmt, *UserID, Email)
Now to dynamically determine the table name User02
based on the userid, I think you will have to use Sprintf
. However you have control of the UserID
and numTables
which are both int. You do not have control over email that may include a single quote '
which can ruin entire query.
https://www.calhoun.io/what-is-sql-injection-and-how-do-i-avoid-it-in-go/
db.Close() | ||
} | ||
|
||
const numTables int = 5 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice if this was configurable instead of hard coded...
} | ||
|
||
func main() { | ||
port := ":8082" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice if this were configurable instead of hardcoded.
filter *bloom.BloomFilter | ||
} | ||
|
||
func createBloomFilter() *bloomFilter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typically this would be an exported function and instead of create it is idiomatic to use new
type BloomFilter struct {
filter *bloom.BloomFilter // this still is "private" or not exported
}
func NewBloomFilter() *BloomFilter{
...
}
} | ||
} | ||
|
||
func crossCheck(UserID *int, Email string) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function needs to bubble up the errors it encounters. Consider changing this functions return signature
func crossCheck(UserID *int, Email string) (bool, error) {...}
This way if there is an error (e.g. with the database connection) then this would return an error, instead of false
which may or may not be the case.
rows, err := db.Query(stmt) | ||
if err != nil { | ||
fmt.Printf("Error from Database Connection") | ||
return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning false says the email is not in the database. But we don't know at this point. All we know is there was an error with communicating with the database
const numTables int = 5 | ||
|
||
stmt := fmt.Sprintf("SELECT uid, email FROM User%02d WHERE uid=%d AND email='%s'", (*UserID)%numTables, *UserID, Email) | ||
rows, err := db.Query(stmt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider using db.QueryRow()
return false | ||
} | ||
|
||
return rows.Next() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eventually we need to call rows.Close()
in order to return the connection back to the db conn pool. Take a look at pg 6-11 The_Ultimate_Guide_To_Building_Database-Driven_Apps_with_Go.pdf It's a quick read and it explains what is going behind the scenes pretty well.
In any case consider
ret := rows.Next()
rows.Close()
return ret
fmt.Println("Starting Client") | ||
|
||
// populate the MySQL Database | ||
db := client.PopulateDB() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The flow of this seems odd. It looks like client.PopulateDB
creates a database object and doesn't close it, but we close it here. I think we should have another function func NewDB() (*sql.DB, error)
that creates the db conn pool. Then if there is no error, you can pass db variable to PopulateDB
, and DropTables
functions.
defer db.Close() | ||
|
||
// populate the Bloom Filter from values in the MySQL Database | ||
client.Populate() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This returns an error. We should check it.
client.Populate() | ||
|
||
// to check if values are in the Bloom Filter. Note: Remember to replace the value of b and the userID in Check to what they are in the MySQL DB. | ||
// b := []string{"[email protected]"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dead code ?
return db | ||
} | ||
|
||
func DropTables(db *sql.DB) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is kinda scary...Why do we want to do this? The database is the only source of truth we have. For our use case, the tables hold the unsubscribe emails, and we def don't want to remove them.
const numTables int = 5 | ||
|
||
func createTable(numTable int, db *sql.DB) error { | ||
stmt := fmt.Sprintf(`CREATE TABLE IF NOT EXISTS User%02d ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The table names are User01
, User02
, ... which makes it seem like we only have 5 users. I think we want them to be Unsub01
, Unsub02
, ...
|
||
defer stmtHandle.Close() | ||
|
||
for i := 0; i < len(usr.Email); i++ { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could also use range
https://gobyexample.com/range
db, err := sql.Open("mysql", "root:SendGrid@tcp(localhost:3306)/UserStructs") | ||
if err != nil { | ||
fmt.Printf("Failed to get handle\n") | ||
db.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there was an err on sql.Open
, we are not guaranteed that db
is not nil. I.e. db.Close
will also error, and possibly it might be a nil pointer err. Instead we should bubble up the error and exit the function.
err = db.Ping() | ||
if err != nil { | ||
fmt.Println(err) | ||
db.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here db.Close
is the right thing todo, however we should bubble up the error and exit the function. Otherwise on line 75 insertToTables
will attempt to connect to a closed connection.
err := createTable(i, db) | ||
if err != nil { | ||
fmt.Println(err) | ||
db.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bubble up err, and exit function... also consider defer db.Close()
, immediately after creating the db
successfully
body, err := ioutil.ReadAll(r.Body) | ||
if err != nil { | ||
w.WriteHeader(http.StatusBadRequest) | ||
w.Write([]byte("Could not read the body of the request")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return from function
|
||
func (bf *bloomFilter) populateBF(w http.ResponseWriter, r *http.Request) { | ||
if r.Body == nil { | ||
w.Write([]byte("Could not read the body of the request")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will register as http.StatusOK. You need to write the header you want here, and if it is an error case, I would return from the function
body, err := ioutil.ReadAll(r.Body) | ||
if err != nil { | ||
w.WriteHeader(http.StatusBadRequest) | ||
w.Write([]byte("Could not read the body of the request")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return from function
stmt := fmt.Sprintf("SELECT TABLE_NAME AS tableName FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' AND TABLE_SCHEMA='UserStructs'") | ||
rows, err := db.Query(stmt) | ||
if err != nil { | ||
fmt.Println("Error from Database Connection") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bubble up error
err = db.Ping() | ||
if err != nil { | ||
fmt.Println(err) | ||
db.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bubble up error
db, err := sql.Open("mysql", "root:SendGrid@tcp(localhost:3306)/UserStructs") | ||
if err != nil { | ||
fmt.Printf("Failed to get handle\n") | ||
db.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bubble up error
stmt := fmt.Sprintf("SELECT uid, email FROM UserStructs.%s", tableName) | ||
rows, err := db.Query(stmt) | ||
if err != nil { | ||
fmt.Printf("Error from Database Connection") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bubble up error
} else { | ||
userMap[&id] = []string{email} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to call rows.Close
to return the connection back to the db conn pool.
Email []string | ||
} | ||
|
||
func Check(userID int, emails []string) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this function being called? Also should it return a list of users that are in the bf?
return nil | ||
} | ||
|
||
func Clear() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again where is this being called?
} | ||
|
||
func Populate() error { | ||
db, err := sql.Open("mysql", "root:SendGrid@tcp(localhost:3306)/UserStructs") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider passing in the credentials as arguments instead of hard coding them
These are the things we need code review with:
We changed the file structure as well (where each file goes) and we were wondering if this is fine?