diff --git a/datastructures/README.md b/datastructures/README.md new file mode 100644 index 0000000..fabe3c9 --- /dev/null +++ b/datastructures/README.md @@ -0,0 +1,7 @@ +# Basic Data Structures + +These are basic implementations of standard data structures. They are not thread safe implementations. To avoid race conditions you would need to use a mutex (mutual exclusion lock) to lock the data structure before any reads or writes. The `sync` package has a `RWMutex` that can be used. + + +## Example of using Mutex + diff --git a/datastructures/go.mod b/datastructures/go.mod new file mode 100644 index 0000000..eb1324b --- /dev/null +++ b/datastructures/go.mod @@ -0,0 +1,3 @@ +module datastructures + +go 1.23.1 diff --git a/datastructures/queue.go b/datastructures/queue.go new file mode 100644 index 0000000..271005f --- /dev/null +++ b/datastructures/queue.go @@ -0,0 +1,41 @@ +package datastructures + +import "errors" + +// Queue a First in First out data structure. +type Queue struct { + elements []any +} + +// Enqueue adds and element to the queue +func (q *Queue) Enqueue(el any) { + q.elements = append(q.elements, el) +} + +// Dequeue removes an element from the queue +func (q *Queue) Dequeue() (any, error) { + if q.IsEmpty() { + return nil, errors.New("empty queue") + } + el := q.elements[0] + q.elements = q.elements[1:] + return el, nil +} + +// Peek retrieves a copy of the next element in the queue. +func (q *Queue) Peek() (any, error) { + if q.IsEmpty() { + return nil, errors.New("empty queue") + } + return q.elements[0], nil +} + +// IsEmpty checks if the queue has any elements +func (q *Queue) IsEmpty() bool { + return q.Size() == 0 +} + +// Size checks the size of the queue +func (q *Queue) Size() int { + return len(q.elements) +} diff --git a/datastructures/set.go b/datastructures/set.go new file mode 100644 index 0000000..b8d5390 --- /dev/null +++ b/datastructures/set.go @@ -0,0 +1,91 @@ +package datastructures + +type Set struct { + elements map[any]bool +} + +func (s *Set) Add(el any) { + s.elements[el] = true +} + +func (s *Set) Remove(el any) { + delete(s.elements, el) +} + +func (s *Set) IsEmpty() bool { + return s.Size() == 0 +} + +func (s *Set) Size() int { + return len(s.elements) +} + +func (s *Set) Has(el any) bool { + _, ok := s.elements[el] + return ok +} + +func (s *Set) Difference(set Set) Set { + d := s.Copy() + for k := range d.elements { + if set.Has(k) { + d.Remove(k) + } + } + + return d +} + +func (s *Set) IsSubset(t Set) bool { + for k := range s.elements { + if !t.Has(k) { + return false + } + } + return true +} + +func (s *Set) List() []any { + list := make([]any, s.Size()) + for k := range s.elements { + list = append(list, k) + } + return list +} + +func (s *Set) Copy() Set { + c := NewSet() + for k := range s.elements { + c.Add(k) + } + return c +} + +// NewSet creates a new instance of Set +func NewSet() Set { + return Set{elements: make(map[any]bool)} +} + +// Union determines the OR relationship on all sets under consideration +func Union(sets ...Set) Set { + u := sets[0].Copy() + for _, set := range sets[1:] { + for k := range set.elements { + u.Add(k) + } + } + return u +} + +// Intersection determines the AND relationship of all sets under consideration +func Intersection(sets ...Set) Set { + i := sets[0].Copy() + for k := range i.elements { + for _, set := range sets[1:] { + if !set.Has(k) { + i.Remove(k) + } + } + } + return i +} diff --git a/datastructures/stack.go b/datastructures/stack.go new file mode 100644 index 0000000..e4252f7 --- /dev/null +++ b/datastructures/stack.go @@ -0,0 +1,66 @@ +package datastructures + +import "errors" + +// Stack a Last in First out data structure. +type Stack struct { + elements []any +} + +// Push add an element to the top of the stack +// +// Parameters: +// +// any: an element to add to the top of the stack. +func (s *Stack) Push(el any) { + s.elements = append(s.elements, el) +} + +// Pop removes and returns the top element of the stack. +// +// Returns: +// +// any: The top element of the stack +// +// error: If the stack is found to be empty +func (s *Stack) Pop() (any, error) { + if s.IsEmpty() { + return nil, errors.New("empty stack") + } + el := s.elements[len(s.elements)-1] + s.elements = s.elements[:len(s.elements)-1] + return el, nil +} + +// Peek checks the top item in the stack. +// +// Returns: +// +// any: a copy of the top element +// +// error: An error if the stack is empty +func (s *Stack) Peek() (any, error) { + if s.IsEmpty() { + return nil, errors.New("empty stack") + } + + return s.elements[len(s.elements)-1], nil +} + +// IsEmpty checks if the stack contains any elements. +// +// Returns: +// +// bool: True is the stack is empty. False if the stack contains elements. +func (s *Stack) IsEmpty() bool { + return s.Size() == 0 +} + +// Size checks this size of the stack +// +// Returns: +// +// int: The number of elements on the stack +func (s *Stack) Size() int { + return len(s.elements) +} diff --git a/debugger/README.md b/debugger/README.md new file mode 100644 index 0000000..90deb3d --- /dev/null +++ b/debugger/README.md @@ -0,0 +1,301 @@ +# Using the Debugger + +Go doesn't ship with a debugger but you can use the delve package instead. + +https://www.youtube.com/watch?v=UA0SirX6Siw + +``` +go get github.com/go-delve/delve/cmd/dlv +``` + +``` +dlv version +Delve Debugger +Version: 1.23.1 +Build: $Id: 2eba762d75437d380e48fc42213853f13aa2904d $ +``` + +## Using dlv + +From within a go module run: + +``` +dlv debug + +Type 'help' for list of commands. +(dlv) +``` + +Now set a breakpoint on the main function + +``` +break main.main +``` + +This breaks not at your main function but in the go runtime. + +Now you can do something like set a break point at a line number in a file + +``` +break main.go:20 +``` + +Now we can continue until we hit a breakpoint + +``` +c or continue + + +> [Breakpoint 1] main.main() ./main.go:19 (hits goroutine(1):1 total:1) (PC: 0x4ae66e) + 14: + 15: func Three() { + 16: fmt.Println("Three!") + 17: } + 18: +=> 19: func main() { + 20: msg := "Hello debugger" + 21: One() + 22: fmt.Println(msg) + 23: + 24: } +``` + +Now we can check for locals + +``` +locals + +(no locals) +``` + +No surprising there are no local variables at this point. + +``` +n +``` + +Will advance us to the next line. But again no locals because that hasn't been executed yet. So `n` again. + +``` +locals + +msg = "Hello debugger" +``` + + + +``` +ls + +> main.main() ./main.go:21 (PC: 0x4ae687) + 16: fmt.Println("Three!") + 17: } + 18: + 19: func main() { + 20: msg := "Hello debugger" +=> 21: One() + 22: fmt.Println(msg) + 23: + 24: } +``` + +Now we can step into our function with `s` + +``` +s + +> main.One() ./main.go:5 (PC: 0x4ae48a) + 1: package main + 2: + 3: import "fmt" + 4: +=> 5: func One() { + 6: Two() + 7: fmt.Println("One!") + 8: } + 9: + 10: func Two() { +``` + +You can se now we have stepped into the function. + +We can `n` and `s` into `Two()`. + +``` +> main.Two() ./main.go:10 (PC: 0x4ae52a) + 5: func One() { + 6: Two() + 7: fmt.Println("One!") + 8: } + 9: +=> 10: func Two() { + 11: Three() + 12: fmt.Println("Two!") + 13: } + 14: + 15: func Three() { +``` + +How about we look at the stack trace at this point + +``` +stack +0 0x00000000004ae52a in main.Two + at ./main.go:10 +1 0x00000000004ae493 in main.One + at ./main.go:6 +2 0x00000000004ae68c in main.main + at ./main.go:21 +3 0x000000000043b647 in runtime.main + at /usr/local/go/src/runtime/proc.go:272 +4 0x0000000000474081 in runtime.goexit + at /usr/local/go/src/runtime/asm_amd64.s:1700 +``` + +Let's `n` and `s` one more time and check the stack trace. + +``` +0 0x00000000004ae5ca in main.Three + at ./main.go:15 +1 0x00000000004ae533 in main.Two + at ./main.go:11 +2 0x00000000004ae493 in main.One + at ./main.go:6 +3 0x00000000004ae68c in main.main + at ./main.go:21 +4 0x000000000043b647 in runtime.main + at /usr/local/go/src/runtime/proc.go:272 +5 0x0000000000474081 in runtime.goexit + at /usr/local/go/src/runtime/asm_amd64.s:1700 +``` + +If we needed to we could us `frame` to go back into the stack and and we can inspect the state of code at that point. + + +``` +frame 3 + +locals +``` + +But then we need to move back to `frame 0` and can continue debugging. + +``` +(dlv) ls +> main.Two() ./main.go:11 (PC: 0x4ae52e) + 6: Two() + 7: fmt.Println("One!") + 8: } + 9: + 10: func Two() { +=> 11: Three() + 12: fmt.Println("Two!") + 13: } + 14: + 15: func Three() { + 16: fmt.Println("Three!") +``` + +Instead of stepping to `Three()` let's step out of Two. + +``` +(dlv) so +Three! +Two! +> main.One() ./main.go:7 (PC: 0x4ae493) +Values returned: + + 2: + 3: import "fmt" + 4: + 5: func One() { + 6: Two() +=> 7: fmt.Println("One!") + 8: } + 9: + 10: func Two() { + 11: Three() + 12: fmt.Println("Two!") +``` + +We see out stdout messages now there is one to go. + +``` +(dlv) stack +0 0x00000000004ae493 in main.One + at ./main.go:7 +1 0x00000000004ae68c in main.main + at ./main.go:21 +2 0x000000000043b647 in runtime.main + at /usr/local/go/src/runtime/proc.go:272 +3 0x0000000000474081 in runtime.goexit + at /usr/local/go/src/runtime/asm_amd64.s:1700 +``` + +We can continue this until our process returns. But when we are back in our main function. Let's look at locals one more time. + +``` + 17: } + 18: + 19: func main() { + 20: msg := "Hello debugger" + 21: One() +=> 22: fmt.Println(msg) + 23: + 24: } + +locals + +msg = "Hello debugger" +``` + +We can't change this variable because Delve wants to avoid operations itself that involves memory allocations, but if we had a + +``` +func setMsg(value string) { + msg = value +} +``` + +We could use `call` to set that variable + +``` +call setMsg("dirp") +``` + +To close it could just `continue` until the process exits. + +``` +(dlv) c + +Process 29036 has exited with status 0 +``` + +## Wait there is more + +Help has tons of stuff + +``` +help +``` + +Like + +``` +restart +``` +Restarts the program of course. Our current breakpoints are still set. + +``` +(dlv) print msg +"Hello debugger" +``` + +And hey this time we used print to check a value. + +Technically you can edit code with `edit`. + + +## Debugging goroutines + + diff --git a/debugger/go.mod b/debugger/go.mod new file mode 100644 index 0000000..f905c7f --- /dev/null +++ b/debugger/go.mod @@ -0,0 +1,3 @@ +module debugger + +go 1.23.1 diff --git a/debugger/main.go b/debugger/main.go new file mode 100644 index 0000000..8bc639a --- /dev/null +++ b/debugger/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "sync" +) + +func One() { + Two() + fmt.Println("One!") +} + +func Two() { + Three() + fmt.Println("Two!") +} + +func Three() { + fmt.Println("Three!") +} + +// Used in the simple example +// func main() { +// msg := "Hello debugger" +// One() +// fmt.Println(msg) + +// } + +// Used for Goroutine debugging + +func printMe(i int, wg *sync.WaitGroup) { + defer wg.Done() + fmt.Printf("I'm a go routine %d\n", i) +} + +func main() { + i := 0 + + var wg sync.WaitGroup + wg.Add(9) + + for i < 10 { + go func(rI int) { + printMe(rI, &wg) + }(i) + i++ + } + + wg.Wait() +} diff --git a/go-sql-database/README.md b/go-sql-database/README.md new file mode 100644 index 0000000..297cebc --- /dev/null +++ b/go-sql-database/README.md @@ -0,0 +1,6 @@ +# Learning SQL DB Access + + +Resources: + +http://go-database-sql.org/index.html It's the missing guide to the docs "how" to use the database/sql package. \ No newline at end of file diff --git a/go-sql-database/try-sqlite/README.md b/go-sql-database/try-sqlite/README.md new file mode 100644 index 0000000..ebed68b --- /dev/null +++ b/go-sql-database/try-sqlite/README.md @@ -0,0 +1,23 @@ +# Regular SQLite in Go + +## Install the Migrations Tool + +This will in stall the migrate tool for our system. + +``` +go install -tags 'sqlite3' github.com/golang-migrate/migrate/v4/cmd/migrate@latest +``` + +You could also use golang-migrate as a library and migrate your DB in code. For that you would need to install that package as part of your module dependencies. + +``` +go get github.com/mattn/go-sqlite3 +``` + +## Running Migrations + +### Using the CLI + +``` +migrate -database sqlite3://./data/trysqlite.db -path ./migrations +``` \ No newline at end of file diff --git a/go-sql-database/try-sqlite/data/trysqlite.db b/go-sql-database/try-sqlite/data/trysqlite.db new file mode 100644 index 0000000..8c05135 Binary files /dev/null and b/go-sql-database/try-sqlite/data/trysqlite.db differ diff --git a/go-sql-database/try-sqlite/data/trysqlite.db-shm b/go-sql-database/try-sqlite/data/trysqlite.db-shm new file mode 100644 index 0000000..fe9ac28 Binary files /dev/null and b/go-sql-database/try-sqlite/data/trysqlite.db-shm differ diff --git a/go-sql-database/try-sqlite/data/trysqlite.db-wal b/go-sql-database/try-sqlite/data/trysqlite.db-wal new file mode 100644 index 0000000..e69de29 diff --git a/go-sql-database/try-sqlite/go.mod b/go-sql-database/try-sqlite/go.mod new file mode 100644 index 0000000..9aff530 --- /dev/null +++ b/go-sql-database/try-sqlite/go.mod @@ -0,0 +1,5 @@ +module try-sqlite + +go 1.21.0 + +require github.com/mattn/go-sqlite3 v1.14.22 // indirect diff --git a/go-sql-database/try-sqlite/go.sum b/go-sql-database/try-sqlite/go.sum new file mode 100644 index 0000000..e8d092a --- /dev/null +++ b/go-sql-database/try-sqlite/go.sum @@ -0,0 +1,2 @@ +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/go-sql-database/try-sqlite/main.go b/go-sql-database/try-sqlite/main.go new file mode 100644 index 0000000..ddf7211 --- /dev/null +++ b/go-sql-database/try-sqlite/main.go @@ -0,0 +1,272 @@ +package main + +import ( + "database/sql" + "flag" + "log" + + _ "github.com/mattn/go-sqlite3" +) + +func showTransactionStmtClose(p *sql.Stmt) { + log.Println("Closing statement!") + p.Close() +} + +func main() { + + log.Println("Hello, world!") + db_path := flag.String("database", "./data/trysqlite.db", "Path to a Sqlite3 database") + flag.Parse() + + // TODO figure out what query string options to be using + full_database_path := "file:" + *db_path + "?cache=shared" + + log.Printf("Using Database: %s", full_database_path) + // db is a Handle backed by a go database/sql connection pool. It is not a connection in of itself, nor is it the actual connection pool + // The first connection is made lazily by the handle, and this doesn't validate any of the connection parameters either at this point. + // It simply prepares the abstraction for use. + + db, err := sql.Open("sqlite3", full_database_path) + if err != nil { + log.Fatalf("Failed to connect to sqlite database: %s", full_database_path) + } + // It is idiomatic to defer db.Close() if the sql.DB should not have a lifetime beyond the scope of the function. + //Although it’s idiomatic to Close() the database when you’re finished with it, the sql.DB object is designed to be long-lived. + //Don’t Open() and Close() databases frequently. Instead, create one sql.DB object for each distinct datastore you need to access, + // and keep it until the program is done accessing that datastore. + + // Pass it around as needed, or make it available somehow globally, but keep it open. + // And don’t Open() and Close() from a short-lived function. Instead, pass the sql.DB + // into that short-lived function as an argument. + + //If you don’t treat the sql.DB as a long-lived object, you could experience problems such as poor reuse and sharing of connections, + // running out of available network resources, or sporadic failures due to a lot of TCP connections remaining in TIME_WAIT status. + // Such problems are signs that you’re not using database/sql as it was designed. + defer db.Close() + + // If you want to check right away that the db is available and accessible you can do this and check for errs + err = db.Ping() + if err != nil { + log.Fatal("DB Ping failed. Check database and database connection parameters") + } + + var ( + id int + subject string + todo string + ) + + // db.Query vs db.Exec. If a function name includes Query, it is designed to ask a question of the database, + // and will return a set of rows, even if it’s empty. Statements that don’t return rows should not use Query functions; + // they should use Exec() + + rows, err := db.Query("SELECT subject FROM todos") + if err != nil { + log.Fatal(err) + + } + // It is important to defer closing a row object also. Why? It will release the memory and the network connection. So it + // also prevents resource leaks. So you are a good steward, you clean up after yourself. + // You don't wait for the garbage collector to do it. + + // as long as there’s an open result set (represented by rows), the underlying connection is busy and can’t be used for + //any other query. It's busy because sql.Rows objects don't retrieve all results a query returns. It does it lazily to prevent over runing memory. + // it depends on the driver but there is usually and internal buffer that stores results. + + // Also never defer in a loop. Defer only runs on function exit. + defer rows.Close() + // Iterate through each. Internally rows.Next will hit an EOF error it will call rows.close() for you freeing up the connection resources. + // but you shouldn't rely on that alone. rows.Close() will run on function exit, but this example is a Main func, so it's not realistic + // still get in the habit + for rows.Next() { + err = rows.Scan(&subject) + if err != nil { + log.Fatal(err) + } + log.Printf("Subject is %s", subject) + } + + // Don’t just assume that the loop iterates until you’ve processed all the rows. Always check. + err = rows.Err() + if err != nil { + log.Fatal(err) + } + + // You could also clean up at this point. The call is supposed to be idempotent, but defer is more idiomatic. + // You don't want to call close if there was an Error because of that could cause a Panic. + rows.Close() + + // If never want to do string concat from user input into a sql statement you run unless you like sql injection attacks + // expects to return one row. If no row it will delay throwing error until Scan is called + row := db.QueryRow("SELECT id, subject, todo FROM todos WHERE id = ?", 1) + // Scan does the heavily lifting for you when casting from db type to go type. It will cast based on the variable type. + // failures of course will be returned as part of the err + err = row.Scan(&id, &subject, &todo) + if err != nil { + log.Fatal(err) + } + + log.Printf("id: %d, subject: %s, todo: %s", id, subject, todo) + + // This should error on scan + // We can also use this short hand! + err = db.QueryRow("SELECT * FROM todos WHERE id = ?", 100_000).Scan(&id, &subject, &todo) + if err != nil { + log.Print(err.Error()) + } + + // There is no row.Close(). Rows objects have a Close() method. No explicit closing required. + + //Prepared Queries + // If you will be using the same query over and over you should just prepare the query. + // $N (e.g. $1, $2, etc...) is the postgres query param which SQLite can use too ? is mysql's + + //Under the hood, db.Query() actually prepares, executes, and closes a prepared statement + // That’s three round-trips to the database. + stmt, err := db.Prepare("SELECT todo as other_todo FROM todos WHERE id = $1") + if err != nil { + log.Fatal(err) + } + + //This prepared statement *sql.Stmt is tied to the database connection from which it was created, and it holds onto that connection until you call stmt.Close() + defer stmt.Close() + + // You can also call with just params the .QueryRow method since the statement is already prepared + var other_todo string + + err = stmt.QueryRow(1).Scan(&other_todo) + if err != nil { + log.Fatal(err) + } + + log.Printf("Other Todo: %s", other_todo) + stmt.Close() + + // MODIFYING DATA + // Use db.Exec() with a prepared statement when INSERTING, UPDATING, or DELETING + // Or another statement that doesn't return rows. + + stmt, err = db.Prepare("INSERT INTO todos (subject, todo) VALUES ($1, $2)") + if err != nil { + log.Fatal(err) + } + defer stmt.Close() + + // stmt.Exec returns a sql.Result. That also has access to the statement metadata + // use _ if you don't care about the result and only want to check the err. + + // Exec will NOT reserve a connection. unlike db.Query which returns a sql.Rows that + // will hold on to a connection until .Close() is called. + res, err := stmt.Exec("programming", "learn golang database queries!") + if err != nil { + log.Print("Dirp...") + log.Fatal(err) + } + + lastId, err := res.LastInsertId() + if err != nil { + log.Fatal(err) + } + + log.Printf("Last inserted id: %d", lastId) + + rowCnt, err := res.RowsAffected() + if err != nil { + log.Fatal(err) + } + + log.Printf("The number of rows affected: %d", rowCnt) + // close insert + stmt.Close() + + stmt, err = db.Prepare("DELETE FROM todos WHERE subject = 'programming'") + if err != nil { + log.Fatal(err) + } + + res, err = stmt.Exec() + if err != nil { + log.Fatal(err) + } + + rowCnt, err = res.RowsAffected() + if err != nil { + log.Fatal(err) + } + + log.Printf("The number of rows affected: %d", rowCnt) + + // TRANSACTIONS + + //In Go, a transaction is essentially an object that reserves a connection to the datastore. + //It lets you do all of the operations we’ve seen thus far, but guarantees that they’ll be + //executed on the same connection. + + // Starts with db.Begin() returning a sql.Tx and ends with a .Commit() or .Rollback() on the results sql.Tx + + tx, err := db.Begin() + if err != nil { + log.Fatal(err) + } + + // Prepared statement in a transaction are bound to that transaction exclusively. + + // DO NOT use sql BEGIN or COMMIT in a sql.Tx object. It could leave the DB and client in different states. + // or mess havoc with the connection pool and connection lifetime + insert_stmt, err := tx.Prepare("INSERT INTO todos (subject, todo) VALUES ($1, $2)") + if err != nil { + log.Fatal(err) + } + defer insert_stmt.Close() // This closes at the end of the transaction, not the end of the function. + + _, _ = insert_stmt.Exec("dirp", "just a place holder") + + // There is no Close method on a transaction. It is closed by using .Commit or .Rollback + tx.Rollback() + + // PREPARED STATEMENTS IN MORE DEPTH + + //At the database level, a prepared statement is bound to a single database connection. + //The typical flow is that the client sends a SQL statement with placeholders to the server + //for preparation, the server responds with a statement ID, and then the client executes + //the statement by sending its ID and parameters. + + // THE ABOVE DOES NOT HAPPEN IN database/sql. Instead the statement is managed at the driver level + // Go doesn't expose connections directly to users. The sql.Stmt object remembers which connection + // from the pool it is associated with. + + //Know that if a connection is busy it may automatically get a new connection and re-prepare the + // statement on a new connection. It could cause more connections than you think, more statement + // recreation, or even hitting a server limit on prepared statements. + + // NOTE db.Query(sql, param1, param2) will create a prepared statement under the hood. + // If you don’t want to use a prepared statement, you need to use fmt.Sprint() or similar to + //assemble the SQL, and pass this as the only argument to db.Query() or db.QueryRow() And your driver + //needs to support plaintext query execution, + + // NOTE prepared statements on Tx are bound exclusively to the Tx. You cannot use a prepared statement + // created by db.Prepare nor can you use a Tx.Prepare statement with a db.Query. Tx.Stmt() can be used + // to copy a db.Prepared statement into a transaction, but don't do this. + + // HANDLING ERRORS + + // You should always explicitly close a sql.Rows result set. If rows.Close() returns an error, it’s + // unclear what you should do, so logging the error message and ignoring or panic-ing might be the only sensible + // thing to do. + + // Use sql.ErrNoRows in the event that no data is returned. + // rememeber db.QueryRow defers errors till Scan is called. + err = db.QueryRow("select subject from todos where id = $1", 100_000).Scan(&subject) + if err != nil { + if err == sql.ErrNoRows { + // there were no rows, but otherwise no error occurred + log.Println("No rows were returned! But that is okay. We expected that could happen") + } else { + log.Fatal(err) + } + } + + // Each driver has it's own error types + +} diff --git a/go-sql-database/try-sqlite/migrations/10_todos.down.sql b/go-sql-database/try-sqlite/migrations/10_todos.down.sql new file mode 100644 index 0000000..4649281 --- /dev/null +++ b/go-sql-database/try-sqlite/migrations/10_todos.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS todos; \ No newline at end of file diff --git a/go-sql-database/try-sqlite/migrations/10_todos.up.sql b/go-sql-database/try-sqlite/migrations/10_todos.up.sql new file mode 100644 index 0000000..bab4b97 --- /dev/null +++ b/go-sql-database/try-sqlite/migrations/10_todos.up.sql @@ -0,0 +1,2 @@ +PRAGMA foreign_keys=1; +CREATE TABLE todos (id INTEGER PRIMARY KEY AUTOINCREMENT, subject TEXT, TODO TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP); \ No newline at end of file diff --git a/go-sql-database/try-sqlite/migrations/20_alter_todos_completed.down.sql b/go-sql-database/try-sqlite/migrations/20_alter_todos_completed.down.sql new file mode 100644 index 0000000..8ed2cc8 --- /dev/null +++ b/go-sql-database/try-sqlite/migrations/20_alter_todos_completed.down.sql @@ -0,0 +1 @@ +ALTER TABLE todos DROP COLUMN completed; \ No newline at end of file diff --git a/go-sql-database/try-sqlite/migrations/20_alter_todos_completed.up.sql b/go-sql-database/try-sqlite/migrations/20_alter_todos_completed.up.sql new file mode 100644 index 0000000..80fd19d --- /dev/null +++ b/go-sql-database/try-sqlite/migrations/20_alter_todos_completed.up.sql @@ -0,0 +1 @@ +ALTER TABLE todos ADD completed BOOL DEFAULT false; \ No newline at end of file diff --git a/go-web-app/README.md b/go-web-app/README.md index d7c6d6d..0a7faaf 100644 --- a/go-web-app/README.md +++ b/go-web-app/README.md @@ -19,3 +19,15 @@ I've taken this project a bit further then the article. Follow on work can inclu - [ ] Runs test suite - [ ] Tarball release artifact on Promote - [ ] Deb release artifact on Promote + + +## Adding Libsql to a project + +First install the driver + +``` +go get github.com/tursodatabase/go-libsql +``` + +We will only use a local file for this project. We will will use `GO_WIKI_DB` environment variable to determine where the SQLite file is located. It will default to ./data like the previous file based system. + diff --git a/go-web-app/gowiki/go.mod b/go-web-app/gowiki/go.mod index f9391db..328689e 100644 --- a/go-web-app/gowiki/go.mod +++ b/go-web-app/gowiki/go.mod @@ -1,3 +1,10 @@ module gowiki go 1.21.0 + +require ( + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 // indirect + github.com/tursodatabase/go-libsql v0.0.0-20240429120401-651096bbee0b // indirect + golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect +) diff --git a/go-web-app/gowiki/go.sum b/go-web-app/gowiki/go.sum new file mode 100644 index 0000000..98766cb --- /dev/null +++ b/go-web-app/gowiki/go.sum @@ -0,0 +1,8 @@ +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 h1:JLvn7D+wXjH9g4Jsjo+VqmzTUpl/LX7vfr6VOfSWTdM= +github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06/go.mod h1:FUkZ5OHjlGPjnM2UyGJz9TypXQFgYqw6AFNO1UiROTM= +github.com/tursodatabase/go-libsql v0.0.0-20240429120401-651096bbee0b h1:R7hev4b96zgXjKbS2ZNbHBnDvyFZhH+LlMqtKH6hIkU= +github.com/tursodatabase/go-libsql v0.0.0-20240429120401-651096bbee0b/go.mod h1:TjsB2miB8RW2Sse8sdxzVTdeGlx74GloD5zJYUC38d8= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= diff --git a/learn_go_with_tests/concurrency/CheckWebsites.go b/learn_go_with_tests/concurrency/CheckWebsites.go new file mode 100644 index 0000000..511d484 --- /dev/null +++ b/learn_go_with_tests/concurrency/CheckWebsites.go @@ -0,0 +1,36 @@ +package concurrency + +type WebsiteChecker func(string) bool +type result struct { + string + bool +} + +func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool { + results := make(map[string]bool) + resultChannel := make(chan result) + + for _, url := range urls { + // anonymous function call required because go routines must call + // a function to start. An anonymous function maintains access to + // the lexical scope in which they are defined - all the variables + // that are available at the point when you declare the anonymous + //function are also available in the body of the function. + // url := url // create a new variable to avoid capturing the loop variable. + // go func() { + // results[url] = wc(url) + // }() + go func(u string) { + resultChannel <- result{u, wc(u)} // send statement + }(url) // or just pass by value to give the func it's own url + } + + // Improvement idea. We could use a wait group here. Instead we are blocking + // each go routine because we are using an unbuffered channel. + for i := 0; i < len(urls); i++ { + r := <-resultChannel // receive statement + results[r.string] = r.bool + } + + return results +} diff --git a/learn_go_with_tests/concurrency/CheckWebsites_test.go b/learn_go_with_tests/concurrency/CheckWebsites_test.go new file mode 100644 index 0000000..be58d15 --- /dev/null +++ b/learn_go_with_tests/concurrency/CheckWebsites_test.go @@ -0,0 +1,48 @@ +package concurrency + +import ( + "reflect" + "testing" + "time" +) + +func mockWebsiteChecker(url string) bool { + return url != "waat://furhurterwe.geds" +} + +func slowStubWebsiteChecker(_ string) bool { + time.Sleep(20 * time.Millisecond) + return true +} + +// What does testing.B do? +func BenchmarkCheckWebsites(b *testing.B) { + urls := make([]string, 100) + for i := 0; i < len(urls); i++ { + urls[i] = "a url" + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + CheckWebsites(slowStubWebsiteChecker, urls) + } +} + +func TestWebsites(t *testing.T) { + websites := []string{ + "http://google.com", + "http://hackaday.com", + "waat://furhurterwe.geds", + } + + want := map[string]bool{ + "http://google.com": true, + "http://hackaday.com": true, + "waat://furhurterwe.geds": false, + } + + got := CheckWebsites(mockWebsiteChecker, websites) + + if !reflect.DeepEqual(want, got) { + t.Fatalf("Wanted %v, got %v", want, got) + } +} diff --git a/learn_go_with_tests/concurrency/READMD.md b/learn_go_with_tests/concurrency/READMD.md new file mode 100644 index 0000000..5efa9ec --- /dev/null +++ b/learn_go_with_tests/concurrency/READMD.md @@ -0,0 +1,9 @@ +# Concurrency + +https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/concurrency + +This was an interesting chapter. The high-level takeaways: + +- An anonymous function maintains access to the lexical scope in which they are defined +- Go can help identify race conditions with [race detector](https://blog.golang.org/race-detector) `go test -race` +- Coordinating go routines can be accomplished with channels. Channels are a data structure that can both receive and send values. This allows cross go routine communication. Channels have a type, and you will commonly see structs passed around. \ No newline at end of file diff --git a/learn_go_with_tests/concurrency/go.mod b/learn_go_with_tests/concurrency/go.mod new file mode 100644 index 0000000..88825ae --- /dev/null +++ b/learn_go_with_tests/concurrency/go.mod @@ -0,0 +1,3 @@ +module concurrency + +go 1.22.5 diff --git a/learn_go_with_tests/my_select/go.mod b/learn_go_with_tests/my_select/go.mod new file mode 100644 index 0000000..696d9bc --- /dev/null +++ b/learn_go_with_tests/my_select/go.mod @@ -0,0 +1,3 @@ +module my_select + +go 1.22.5 diff --git a/learn_go_with_tests/my_select/racer.go b/learn_go_with_tests/my_select/racer.go new file mode 100644 index 0000000..305a610 --- /dev/null +++ b/learn_go_with_tests/my_select/racer.go @@ -0,0 +1,66 @@ +package my_select + +import ( + "fmt" + "net/http" + "time" +) + +var defaultTimeout time.Duration = 10 * time.Second + +func Racer(a, b string) (winner string, err error) { + return ConfigurableRacer(a, b, defaultTimeout) +} + +func ConfigurableRacer(a string, b string, timeout time.Duration) (winner string, err error) { + // aDuration := measureResponseTime(a) + // bDuration := measureResponseTime(b) + + // if aDuration < bDuration { + // return a + // } + + // return b + + select { + // Here we are simply waiting on the channel closing out + // The first one that does so will return it's url + + // These are blocking operations because they are unbuffered channels. + // This works though because `select` allows us to wait on multiple channels + case <-ping(a): + return a, nil + case <-ping(b): + return b, nil + // Super handy function during select, returns a channel, waits the timeout, then + //sends the current time that it was triggered at. + // Helps get us out of a blocking case. + case <-time.After(timeout): + return "", fmt.Errorf("timed out waiting for %s and %s", a, b) + } +} + +// In our case, we don't care what type is sent to +//the channel, we just want to signal we are done +// and closing the channel works perfectly! + +// a chan struct{} is the smallest data type available +// from a memory perspective + +func ping(url string) chan struct{} { + // Notice how we have to use make when creating a channel; rather than say var ch chan struct{}. When you use var the variable will be initialised with the "zero" value of the type. So for string it is "", int it is 0, etc. + + // For channels the zero value is nil and if you try and send to it with <- it will block forever because you cannot send to nil channels + ch := make(chan struct{}) + go func() { + http.Get(url) + close(ch) + }() + return ch +} + +// func measureResponseTime(url string) time.Duration { +// start := time.Now() +// http.Get(url) +// return time.Since(start) +// } diff --git a/learn_go_with_tests/my_select/racer_test.go b/learn_go_with_tests/my_select/racer_test.go new file mode 100644 index 0000000..7262ab6 --- /dev/null +++ b/learn_go_with_tests/my_select/racer_test.go @@ -0,0 +1,51 @@ +package my_select + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func TestRacer(t *testing.T) { + + t.Run("compares speeds of servers, returning url of the fastest one", func(t *testing.T) { + slowServer := makeDelayedServer(20 * time.Microsecond) + defer slowServer.Close() + + fastServer := makeDelayedServer(0 * time.Microsecond) + defer fastServer.Close() + + slowUrl := slowServer.URL + fastUrl := fastServer.URL + + want := fastUrl + got, _ := Racer(slowUrl, fastUrl) + + if got != want { + t.Errorf("got %q, want %q", got, want) + } + }) + + t.Run("returns an error if server doesn't respond within 10s", func(t *testing.T) { + serverA := makeDelayedServer(50 * time.Millisecond) + serverB := makeDelayedServer(60 * time.Millisecond) + + defer serverA.Close() + defer serverB.Close() + + _, err := ConfigurableRacer(serverA.URL, serverB.URL, 10*time.Millisecond) + + if err == nil { + t.Error("expected an error but didn't get one") + } + + }) +} + +func makeDelayedServer(delay time.Duration) *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(delay) + w.WriteHeader(http.StatusOK) + })) +} diff --git a/learn_go_with_tests/my_sync/go.mod b/learn_go_with_tests/my_sync/go.mod new file mode 100644 index 0000000..e52cb81 --- /dev/null +++ b/learn_go_with_tests/my_sync/go.mod @@ -0,0 +1,3 @@ +module my_sync + +go 1.23.1 diff --git a/learn_go_with_tests/my_sync/my_sync.go b/learn_go_with_tests/my_sync/my_sync.go new file mode 100644 index 0000000..97f0e9d --- /dev/null +++ b/learn_go_with_tests/my_sync/my_sync.go @@ -0,0 +1,23 @@ +package my_sync + +import "sync" + +type Counter struct { + // mutual exclusion lock. Zero value is an unlocked Mutex + + // If we embedded the type it would be c.Lock() but that also makes all other + // Mutex functions part of the public interface, which we don't want. + // sync.Mutex + mu sync.Mutex + count int +} + +func (c *Counter) Inc() { + c.mu.Lock() + defer c.mu.Unlock() + c.count++ +} + +func (c *Counter) Value() int { + return c.count +} diff --git a/learn_go_with_tests/my_sync/my_sync_test.go b/learn_go_with_tests/my_sync/my_sync_test.go new file mode 100644 index 0000000..acfbd99 --- /dev/null +++ b/learn_go_with_tests/my_sync/my_sync_test.go @@ -0,0 +1,63 @@ +package my_sync + +// READ https://go.dev/wiki/MutexOrChannel. The gist is that go prefers to share data by +// communicating, not by sharing. This mean stylistically you will see a lot more chan than Mutex + +// Channels are good for +// passing ownership of data, +// distributing units of work, +// communicating async results + +// Mutexes are good for caches, state. + +import ( + "sync" + "testing" +) + +// go vet +// # my_sync +// # [my_sync] +// ./my_sync_test.go:8:36: assertCount passes lock by value: my_sync.Counter contains sync.Mutex +// ./my_sync_test.go:22:18: call of assertCount copies lock value: my_sync.Counter contains sync.Mutex +// ./my_sync_test.go:42:18: call of assertCount copies lock value: my_sync.Counter contains sync.Mutex +// We don't want to copy a mutex. So this should be changed to take a pointer to a counter + +func assertCount(t testing.TB, got *Counter, want int) { + t.Helper() + if got.Value() != want { + t.Errorf("got %d, want %d", got.Value(), want) + } +} + +func TestCounter(t *testing.T) { + t.Run("Incrementing the counter 3 times leaves it at 3", func(t *testing.T) { + // We could also implemented a `func NewCounter() *Counter {}` if we wanted to. + counter := Counter{} + counter.Inc() + counter.Inc() + counter.Inc() + + assertCount(t, &counter, 3) + + }) + + t.Run("it runs safely concurrently", func(t *testing.T) { + wantedCount := 1000 + counter := Counter{} + + var wg sync.WaitGroup + wg.Add(wantedCount) + + for i := 0; i < wantedCount; i++ { + go func() { + counter.Inc() + wg.Done() + }() + } + // blocking call to wait for all goroutines + wg.Wait() + + assertCount(t, &counter, wantedCount) + }) +} diff --git a/learn_go_with_tests/reflection/go.mod b/learn_go_with_tests/reflection/go.mod new file mode 100644 index 0000000..fae5224 --- /dev/null +++ b/learn_go_with_tests/reflection/go.mod @@ -0,0 +1,3 @@ +module reflection + +go 1.23.1 diff --git a/learn_go_with_tests/reflection/reflection.go b/learn_go_with_tests/reflection/reflection.go new file mode 100644 index 0000000..daf122f --- /dev/null +++ b/learn_go_with_tests/reflection/reflection.go @@ -0,0 +1,99 @@ +package reflection + +import "reflect" + +// Go allows for `interface{}` which we can think as as just +// any type. (any is just an alias for interface{}) this allows for +// some metaprogramming features in the language reflection being one +// of them. Reflection allows us to examine structure at runtime. + +// The downside, you lose type safety and performance impact. + +// Only use reflection if you really need to. + +// See also https://blog.golang.org/laws-of-reflection + +func walk(x interface{}, fn func(input string)) { + // This code is very unsafe and very naive, + val := getValue(x) + + // if val.Kind() == reflect.Slice { + // for i := 0; i < val.Len(); i++ { + // walk(val.Index(i).Interface(), fn) + // } + // return + // } + + // // This would panic if there were no fields + // field := val.Field(0) + // // This would be wrong if the field had any value other than a string + // fn(field.String()) + + // // change for test table ( will fail case 2) + // for i := 0; i < val.NumField(); i++ { + // field := val.Field(i) + // fn(field.String()) + // } + + // THIS GOT messy + // for i := 0; i < val.NumField(); i++ { + // field := val.Field(i) + + // // if field.Kind() == reflect.String { + // // fn(field.String()) + // // } + + // // if field.Kind() == reflect.Struct { + // // // it got recursive + // // walk(field.Interface(), fn) + // // } + + // switch field.Kind() { + // case reflect.String: + // fn(field.String()) + // case reflect.Struct: + // walk(field.Interface(), fn) + // } + // } + + switch val.Kind() { + case reflect.Struct: + for i := 0; i < val.NumField(); i++ { + walk(val.Field(i).Interface(), fn) + } + case reflect.Slice, reflect.Array: + for i := 0; i < val.Len(); i++ { + walk(val.Index(i).Interface(), fn) + } + case reflect.Map: + for _, key := range val.MapKeys() { + walk(val.MapIndex(key).Interface(), fn) + } + + case reflect.Chan: + for { + if v, ok := val.Recv(); ok { + walk(v.Interface(), fn) + } else { + break + } + } + case reflect.Func: + valFnResult := val.Call(nil) + for _, res := range valFnResult { + walk(res.Interface(), fn) + } + case reflect.String: + fn(val.String()) + } +} + +func getValue(x interface{}) reflect.Value { + val := reflect.ValueOf(x) + + if val.Kind() == reflect.Pointer { + val = val.Elem() + } + + return val +} diff --git a/learn_go_with_tests/reflection/reflection_test.go b/learn_go_with_tests/reflection/reflection_test.go new file mode 100644 index 0000000..85dc83a --- /dev/null +++ b/learn_go_with_tests/reflection/reflection_test.go @@ -0,0 +1,177 @@ +package reflection + +import ( + "reflect" + "testing" +) + +type Person struct { + Name string + Profile Profile +} + +type Profile struct { + Age int + City string +} + +func assertContains(t testing.TB, haystack []string, needle string) { + t.Helper() + contains := false + for _, x := range haystack { + if x == needle { + contains = true + } + } + if !contains { + t.Errorf("expected %v to contain %q but it didn't", haystack, needle) + } +} + +func TestWalk(t *testing.T) { + + cases := []struct { + Name string + Input interface{} + ExpectedCalls []string + }{ + // case 0 + { + Name: "struct with on string field", + Input: struct{ Name string }{"Drew"}, + ExpectedCalls: []string{"Drew"}, + }, + // case 1 + { + "struct with two string fields", + struct { + Name string + City string + }{"Chris", "London"}, + []string{"Chris", "London"}, + }, + // case 2 + { + "struct with non string field", + struct { + Name string + Age int + }{"Chris", 33}, + []string{"Chris"}, + }, + // case 3 Nested Struct + { + "nested fields", + Person{ + "Chris", + Profile{33, "London"}, + }, + []string{"Chris", "London"}, + }, + // case 4 struct as pointer + { + "pointers to things", + &Person{ + "Chris", + Profile{33, "London"}, + }, + []string{"Chris", "London"}, + }, + // case 5 slice of profiles + { + "slices", + []Profile{ + {33, "London"}, + {34, "Reykjavík"}, + }, + []string{"London", "Reykjavík"}, + }, + // case 6 array + { + "arrays", + [2]Profile{ + {33, "London"}, + {34, "Reykjavík"}, + }, + []string{"London", "Reykjavík"}, + }, + // case 7 map THIS HAS A GOTCHA + // maps in go do not guarantee order. It will eventually fake + // moved to a separate test run to handle that case + // { + // "maps", + // map[string]string{ + // "Cow": "Moo", + // "Sheep": "Baa", + // }, + // []string{"Moo", "Baa"}, + // }, + } + + for _, test := range cases { + t.Run(test.Name, func(t *testing.T) { + var got []string + walk(test.Input, func(input string) { + got = append(got, input) + }) + + if !reflect.DeepEqual(got, test.ExpectedCalls) { + t.Errorf("get %v, want %v", got, test.ExpectedCalls) + } + }) + + } + + t.Run("with mas", func(t *testing.T) { + aMap := map[string]string{ + "Cow": "Moo", + "Sheep": "Baa", + } + + var got []string + walk(aMap, func(input string) { + got = append(got, input) + }) + + assertContains(t, got, "Moo") + assertContains(t, got, "Baa") + }) + + t.Run("with channels", func(t *testing.T) { + aChannel := make(chan Profile) + + go func() { + aChannel <- Profile{33, "Berlin"} + aChannel <- Profile{34, "Katowice"} + close(aChannel) + }() + + var got []string + want := []string{"Berlin", "Katowice"} + + walk(aChannel, func(input string) { + got = append(got, input) + }) + + if !reflect.DeepEqual(got, want) { + t.Errorf("got %v, want %v", got, want) + } + }) + + t.Run("with function", func(t *testing.T) { + aFunction := func() (Profile, Profile) { + return Profile{33, "Berlin"}, Profile{34, "Katowice"} + } + + var got []string + want := []string{"Berlin", "Katowice"} + + walk(aFunction, func(input string) { + got = append(got, input) + }) + + if !reflect.DeepEqual(got, want) { + t.Errorf("got %v, want %v", got, want) + } + }) +}