Skip to content

Commit

Permalink
simplified docs and removed redundant texts.
Browse files Browse the repository at this point in the history
  • Loading branch information
bengarrett committed Jun 12, 2024
1 parent 612a34a commit 9017e95
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 570 deletions.
11 changes: 10 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,21 @@ task testr

### Documentation

The application configuration documentation can be generated and [viewed in a web browser](http://localhost:8090).
The application configuration documentation can be modified in [`doc.go`](../doc.go) and the changes regenerated and [previewed in a web browser](http://localhost:8090).

```sh
task doc
```

```go
// Copyright © 2023-2024 Ben Garrett. All rights reserved.

/*
The [Defacto2] application is a self-contained web server first devised in 2023.
It is built with the Go language and can be easily compiled for significant server
```
### Building the source code
The source code can be built into a binary for the local machine.
Expand Down
85 changes: 85 additions & 0 deletions docs/database.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Database readme

Previous iterations of the Defacto2 web application relied on MySQL for its database. But for this 2023, Go application rewrite, the site will use [PostgreSQL](https://www.postgresql.org). With the Defacto2 database schema, Postgre uses less memory and is far more performant with complex queries.

Postgre is more strict about data types than MySQL. For example, inserting a string into a numeric column in Postgre will throw an error, whereas MySQL will convert the string to a number. Postgre has a more powerful query optimizer meaning queries often run faster with complex joins and subqueries.

# Table and data <small>changes to implement</small>

These are only suggestions and may not be necessary if they create too much work or complexity.

- [X] Rename `defacto2-inno` database to `defacto2_ps`
- [ ] Rename `files` table to `release` or `releases`
- [ ] Create a `release_tests` table with a selection of 20 read-only records
- [ ] Rename `files.createdat`, `deleteat`, `updatedat` etc to `x_at` aka `create_at`...
- - [ ] __OR__ break convention and use `date_created`, `date_deleted`, `date_updated` etc.

### [datatypes](https://www.postgresql.org/docs/current/datatype.html) differences

- [ ] `CITEXT` type for case-insensitive character strings
- [ ] `files.filesize` should be converted to an `integer`, 4 bytes to permit a 2.147GB value
- [ ] `files.id` should be converted to a `serial` type
- [ ] There is no performance improvement for `fixed-length`, padded character types, etc, meaning strings can use `varchar`(n) or `text`.

### Indexes

- [ ] Create PostgreSQL *indexes* with case-sensitive strings for [optimal performance](https://wirekat.com/optimizing-sql-based-on-postgresql/)?
- [ ] Partial Indexes: Use partial indexes when you only need to index a subset of rows, such as,
- - `CREATE INDEX ON orders (order_date) WHERE status = 'SHIPPED'`;
- [ ] Over-Indexing: Creating too many indexes can slow down write operations, as each index needs to be updated on `INSERT`, `UPDATE`, or `DELETE` operations.
- [ ] Index Maintenance: Rebuild indexes periodically to deal with bloat using `REINDEX`.
- [ ] Indexing Join Columns: Index columns that are used in JOIN conditions to improve join performance.
> `combineGroup` and `(r Role) Distinct()`

### Future idea, _file archive content_ relationship table

Create a relationship files table that contains the filename content within of every archive release.

We could also include columns containing size in bytes, sha256 hash, text body for full text searching.

This would replace the `file_zip_content` column and also, create a CLI tool to scan the archives to fill out this data. For saftey and code maintenance, the tool would need to be a separate program from the web server application.

## Migration from MySQL to PostgreSQL

This document describes how to migrate the Defacto2 MySQL database to PostgreSQL using [pgloader](https://pgloader.io/). Note, the migration is a one-time operation and should be run on a development or staging server before running on the production server.

- `defacto2-inno` is the name of the MySQL database.
- `defacto2_ps` is the name of the PostgreSQL database, note the `_` in the name as opposed to `-`.

Create a migration loader file named `migrate.load` with the following content, replacing the connection strings with your own database credentials:

```sql
LOAD DATABASE
FROM mysql://root:example@localhost:3306/defacto2-inno?useSSL=false
INTO pgsql://root:example@localhost:5432/defacto2_ps

WITH include drop, create tables

ALTER SCHEMA 'source_db' RENAME TO 'public'
;
```

Run the migration using the following command:

```sh
# run the migration
pgloader migrate.load

# test the migration
postgres psql
```

```sql
# SELECT * FROM files;
```

A simple client application to interact with the migrated database is [Postbird](https://github.com/paxa/postbird).

Some more resources:

- [pgloader](https://pgloader.io/)
- [documentation](https://pgloader.readthedocs.io/en/latest/)
- [Docker hub](https://hub.docker.com/r/dimitri/pgloader/)
- [DigitalOcean how-to migrate](https://www.digitalocean.com/community/tutorials/how-to-migrate-mysql-database-to-postgres-using-pgloader)
- [Migrating from MySQL to PostgreSQL](https://pgloader.readthedocs.io/en/latest/tutorial/tutorial.html#migrating-from-mysql-to-postgresql)
194 changes: 194 additions & 0 deletions docs/patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# Patterns

## New

This is a collection of patterns and idioms that could be useful with the new releases of Go.

- https://go.dev/blog/go1.22
- https://tip.golang.org/doc/go1.22
- https://go.dev/blog/go1.21
- https://tip.golang.org/doc/go1.21

### Randomization in Go 1.22

New random package, [`math/rand/v2` package](https://pkg.go.dev/math/rand/v2).

```go
// print a random letter
alpha := []string{
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
fmt.Println(alpha[rand.IntN(len(alpha))])

// print a random number between 0 and 100
fmt.Println(rand.IntN(100))
```

### New Null type in Go 1.22

New type `Null` for [database/sql package](https://pkg.go.dev/database/sql#Null).

```go
var s Null[string]
err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s)
...
if s.Valid {
// use s.V
} else {
// NULL value
}
```

### New version comparison in Go 1.22

New [`go/version` package](https://pkg.go.dev/go/version) for version comparison.

```go
func Compare(x, y string) int
func IsValid(x string) bool
func Lang(x string) string
```

### New built-in functions: min, max, clear in Go 1.21

```go
x, y := 1, 100
m := min(x, y)
M := max(x, y)
c := max(5.0, y, 10)

alpha := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"}
clear(alpha)
```

### New slices package in Go 1.21

New slices package for slice manipulation which are more efficient than `sort.`.
https://pkg.go.dev/slices

```go
smallInts := []int8{0, 42, -10, 8}
slices.Sort(smallInts)
fmt.Println(smallInts)
// Output: [-10 0 8 42]
```

## Database queries

The web application relies on an Object-relational mapping (ORM) implementation provided by [SQLBoiler](https://github.com/volatiletech/sqlboiler) to simplify development.

Some tutorials for SQLBoiler include:

- [SQL in Go with SQLBoiler](https://thedevelopercafe.com/articles/sql-in-go-with-sqlboiler-ac8efc4c5cb8)
- [Introduction to SQLBoiler: Go framework for ORMs](https://blog.logrocket.com/introduction-sqlboiler-go-framework-orms/)

---

Note, because I am lazy, the most of the descriptions below were generated using CoPilot AI.

```go
import(
"github.com/Defacto2/server/internal/postgres/models"
)

got, err := models.Files().One(ctx, db)
if err != nil {
panic(err)
}
fmt.Println("got record:", got.ID)
```

The above code snippet demonstrates how to query a single record from the `files` table. The `ctx` variable is a context.Context object and `db` is a *sql.DB object.

#### Find a record by ID

```go
f := &models.File{ID: 1}
found, err := models.FindFile(ctx, db, f.ID)
if err != nil {
log.DPanic(err)
}
fmt.Println("found record:", found.Filename)
```

The above code snippet demonstrates how to query a single record from the `files` table using a specific ID.

#### Find records by ID

```go
import(
"github.com/Defacto2/server/internal/postgres/models"
"github.com/volatiletech/sqlboiler/v4/queries/qm"
)

found, err := models.Files(qm.Where("id = ?", 1)).One(ctx, db)
if err != nil {
log.DPanic(err)
}
fmt.Println("found record:", found.Filename)
```

The above code snippet also demonstrates how to query a single record from the `files` table using a specific ID. In this case, the query uses a query mod where clause.

#### Check if a record exists

```go
exists, err := models.FileExists(ctx, db, 1)
if err != nil {
log.DPanic(err)
}
fmt.Println("found again exists?:", exists)
```

The above code snippet demonstrates how to check if a record exists in the `files` table.

#### Count records <small>with deleted</small>

```go
count, err := models.Files(qm.WithDeleted()).Count(ctx, db)
if err != nil {
log.DPanic(err)
}
countPub, err := models.Files().Count(ctx, db)
if err != nil {
log.DPanic(err)
}
fmt.Println("total files vs total files that are public:", count, "vs", countPub)
```

The above code snippet demonstrates how to count the number of records in the `files` table. The first query counts all records, including those that have been soft-deleted. The second query counts only records that are not soft-deleted.

#### Raw SQL queries

```go
var users []*models.File
err := models.Files(qm.SQL(`select * from files;`)).Bind(ctx, db, &users)
if err != nil {
log.Fatal(err)
}
// range and print the results
fmt.Print("raw files:")
for i, user := range users {
if i != 0 {
fmt.Print(", ")
}
fmt.Print(user.Filename.String)
}
```

The above code snippet demonstrates how to execute a raw SQL query using SQLBoiler and bind the results to a slice of models.

```go
var users []*models.File
rows, err := db.QueryContext(context.Background(), `select * from files;`)
if err != nil {
log.Fatal(err)
}
err = queries.Bind(rows, &users)
if err != nil {
log.Fatal(err)
}
rows.Close()
```

The above code snippet demonstrates how to execute a raw SQL query and bind the results to a slice of models. The `queries.Bind` function is provided by SQLBoiler.
Loading

0 comments on commit 9017e95

Please sign in to comment.