Daaang Amy
open main menu
Part of series: Learning the Backend

Let's Go Further Ch 6-9

/ 3 min read

A Problem With Adding Migrations

For some reason, I was not able to run the migration as user: ‘greenlight’. context: the book tells you to create a database called ‘greenlight’ and a user called ‘greenlight’.

I have an older copy of the book (ver 1.20.0) ‘Let’s Go Further’ so it probably doesn’t include changes from posgres 15 and up.

Anyways the problem was permissions.

In postgres 15:

PostgreSQL 15 also revokes the CREATE permission from all users except a database owner from the public (or default) schema.

Which will give you the error: ERROR: permission denied for schema public

So to fix this, we need to grant permisions to schemas on public:

GRANT USAGE ON SCHEMA public TO your_user;

This is not the best solution due to security reasons, but you could read more about what Postgres recommends here.

Scanning SQL Rows Into An Object

One thing that the book doesn’t mention is the significance of order. The order of the key-values when you run Scan() matters.

The order of the SQL columns selected should match the order of Scan(). Or else you will get this error when it is run: sql: Scan error on column index 0, name "id": unsupported Scan, storing driver.Value type int64 into type *time.Time

Example:

func (m MovieModel) Get(id int64) (*Movie, error) {
	query := `
	SELECT id, created_at, title, year, runtime, genres, version 
	FROM movies
	WHERE id = $1
	`

	movie := &Movie{}

	err := m.DB.QueryRow(query, id).Scan(
		&movie.ID,
		&movie.CreatedAt,
		&movie.Title,
		&movie.Year,
		&movie.Runtime,
		pq.Array(&movie.Genres),
		&movie.Version,
	)
}

Implementing Optional Filters

Trying to query for filtered data can be a pain. We want our API to be able to filter data but also still use the default values. One way to handle this is to generate a different sql query on runtime but that could make our code confusing. Here is an alternative, take advantage of the SQL language.

Example:

SELECT id, created_at, title, year, runtime, genres, version
FROM movies
WHERE (LOWER(title) = LOWER($1) OR $1 = '')
AND (genres @> $2 OR $2 = '{}')
ORDER BY id

LOWER(title) = LOWER($1) OR $1 = '' will resolve to true if $1 matches the lowercased title or if $1 is empty. This will essentially allow us to grab the correct data despite the missing filters from the API request.

For this to work, when validating filters, any missing filters should be set to a default value.

Other Let’s Go Posts