diff --git a/go-sql-database/README.md b/go-sql-database/README.md new file mode 100644 index 0000000..e69de29 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..51730b8 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..ccc750c --- /dev/null +++ b/go-sql-database/try-sqlite/main.go @@ -0,0 +1,114 @@ +package main + +import ( + "database/sql" + "flag" + "log" + + _ "github.com/mattn/go-sqlite3" +) + +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. + + // 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) + 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 + + row = db.QueryRow("SELECT * FROM todos WHERE id = ?", 100_000) + err = row.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. + +} 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..e69de29 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/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=