1
votes

I'm currently writing a REST API in Golang but I'm getting an error when making a GET request to an endpoint that then retrieves data from a Postgres database table. Specifically, this error is in relation to parsing a postgres array to a golang slice.

The error is as follows: sql: Scan error on column index 8, name "image_paths": pq: parsing array element index 0: pq: scanning to data.Image is not implemented; only sql.Scanner

This is the database with one entry (the relevant column is highlighted): Database Image

The relevant code is as follows:

func (s SpaceDetailsModel) GetForUser(filters Filters, id int64) ([]*SpaceDetails, Metadata, error) {
    if id < 1 {
        return nil, Metadata{}, ErrRecordNotFound
    }

    query := `
        SELECT count(*) OVER(), id, created_At, space_owner_id, location_text, post_code, longitude, latitude, image_paths, space_description, booking_count, number_of_reviews, average_rating, version
        FROM spaces
        WHERE space_owner_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3`

    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    args := []interface{}{id, filters.limit(), filters.offset()}

    rows, err := s.DB.QueryContext(ctx, query, args...)
    if err != nil {
        return nil, Metadata{}, err
    }

    defer rows.Close()

    totalRecords := 0
    spaces := []*SpaceDetails{}

    for rows.Next() {
        var space SpaceDetails

        err := rows.Scan(
            &totalRecords,
            &space.ID,
            &space.CreatedAt,
            &space.SpaceOwnerId,
            &space.LocationText,
            &space.PostCode,
            &space.Coordinates.Longitude,
            &space.Coordinates.Latitude,
            pq.Array(&space.Images),
            &space.SpaceDescription,
            &space.BookingCount,
            &space.NumberOfReviews,
            &space.AverageRating,
            &space.Version,
        )
        if err != nil {
            return nil, Metadata{}, err
        }

        spaces = append(spaces, &space)
    }

    if err = rows.Err(); err != nil {
        return nil, Metadata{}, err
    }

    metadata := calculateMetadata(totalRecords, filters.Page, filters.PageSize)

    return spaces, metadata, nil
}

The stack trace is:

PS C:\Users\bcart\OneDrive\Desktop\Project\Project Z\Code\Parkit> 
go run ./cmd/api
{"level":"INFO","time":"2022-07-28T13:40:14Z","message":"database connection pool established"}
{"level":"INFO","time":"2022-07-28T13:40:14Z","message":"starting server","properties":{"addr":":4000","env":"development"}}
{"level":"ERROR","time":"2022-07-28T13:40:19Z","message":"sql: Scan error on column index 8, name \"image_paths\": pq: parsing array element index 0: pq: scanning to data.Image is not implemented; only sql.Scanner","properties":{"request_method":"POST","request_url":"/api/v1/days_bookings_record"},"trace":"goroutine 22 [running]:\nruntime/debug.Stack(0x31011630, 0xeda7488c3, 0x0)\n\tC:/Program Files/Go/src/runtime/debug/stack.go:24 +0xa5\nparkit/internal/jsonlog.(*Logger).print(0xc000054600, 0xc0000fe001, 0xc0000fe000, 0x99, 0xc00009cc60, 0x0, 0x0, 0x0)\n\tC:/Users/bcart/OneDrive/Desktop/Project/Project Z/Code/Parkit/internal/jsonlog/jsonlog.go:80 +0x475\nparkit/internal/jsonlog.(*Logger).PrintError(0xc000054600, 0xbd9ba0, 0xc00009a2e0, 0xc00009cc60)\n\tC:/Users/bcart/OneDrive/Desktop/Project/Project Z/Code/Parkit/internal/jsonlog/jsonlog.go:52 +0x69\nmain.(*application).logError(0xc0000be000, 0xc0000c6300, 0xbd9ba0, 0xc00009a2e0)\n\tC:/Users/bcart/OneDrive/Desktop/Project/Project Z/Code/Parkit/cmd/api/errors.go:9 +0x145\nmain.(*application).serverErrorResponse(0xc0000be000, 0xbe03e0, 0xc0000883c0, 0xc0000c6300, 0xbd9ba0, 0xc00009a2e0)\n\tC:/Users/bcart/OneDrive/Desktop/Project/Project Z/Code/Parkit/cmd/api/errors.go:26 +0x54\nmain.(*application).createDaysBookingsRecordHandler(0xc0000be000, 0xbe03e0, 0xc0000883c0, 0xc0000c6300)\n\tC:/Users/bcart/OneDrive/Desktop/Project/Project Z/Code/Parkit/cmd/api/spacebookings.go:39 +0x6cd\nnet/http.HandlerFunc.ServeHTTP(0xc0000a4340, 0xbe03e0, 0xc0000883c0, 0xc0000c6300)\n\tC:/Program Files/Go/src/net/http/server.go:2069 +0x4b\ngithub.com/julienschmidt/httprouter.(*Router).Handler.func1(0xbe03e0, 0xc0000883c0, 0xc0000c6300, 0x0, 0x0, 0x0)\n\tC:/Users/bcart/go/pkg/mod/github.com/julienschmidt/[email protected]/router.go:275 +0x1e7\ngithub.com/julienschmidt/httprouter.(*Router).ServeHTTP(0xc000088180, 0xbe03e0, 0xc0000883c0, 0xc0000c6300)\n\tC:/Users/bcart/go/pkg/mod/github.com/julienschmidt/[email protected]/router.go:387 +0xc7e\nmain.(*application).authenticate.func1(0xbe03e0, 0xc0000883c0, 0xc0000c6200)\n\tC:/Users/bcart/OneDrive/Desktop/Project/Project Z/Code/Parkit/cmd/api/middleware.go:128 +0x423\nnet/http.HandlerFunc.ServeHTTP(0xc00009a0a0, 0xbe03e0, 0xc0000883c0, 0xc0000c6200)\n\tC:/Program Files/Go/src/net/http/server.go:2069 +0x4b\nmain.(*application).rateLimit.func2(0xbe03e0, 0xc0000883c0, 0xc0000c6200)\n\tC:/Users/bcart/OneDrive/Desktop/Project/Project Z/Code/Parkit/cmd/api/middleware.go:84 +0x82\nnet/http.HandlerFunc.ServeHTTP(0xc00009c2a0, 0xbe03e0, 0xc0000883c0, 0xc0000c6200)\n\tC:/Program Files/Go/src/net/http/server.go:2069 +0x4b\nmain.(*application).enableCORS.func1(0xbe03e0, 0xc0000883c0, 0xc0000c6200)\n\tC:/Users/bcart/OneDrive/Desktop/Project/Project Z/Code/Parkit/cmd/api/middleware.go:208 +0x3ec\nnet/http.HandlerFunc.ServeHTTP(0xc00009a0c0, 0xbe03e0, 0xc0000883c0, 0xc0000c6200)\n\tC:/Program Files/Go/src/net/http/server.go:2069 +0x4b\nmain.(*application).recoverPanic.func1(0xbe03e0, 0xc0000883c0, 0xc0000c6200)\n\tC:/Users/bcart/OneDrive/Desktop/Project/Project Z/Code/Parkit/cmd/api/middleware.go:30 +0x98\nnet/http.HandlerFunc.ServeHTTP(0xc00009a0e0, 0xbe03e0, 0xc0000883c0, 0xc0000c6200)\n\tC:/Program Files/Go/src/net/http/server.go:2069 +0x4b\ngithub.com/felixge/httpsnoop.CaptureMetrics.func1(0xbe03e0, 0xc0000883c0)\n\tC:/Users/bcart/go/pkg/mod/github.com/felixge/[email protected]/capture_metrics.go:29 +0x53\ngithub.com/felixge/httpsnoop.(*Metrics).CaptureMetrics(0xc0000960a8, 0xbdfe70, 0xc0000c80e0, 0xc000073ab0)\n\tC:/Users/bcart/go/pkg/mod/github.com/felixge/[email protected]/capture_metrics.go:84 +0x1e7\ngithub.com/felixge/httpsnoop.CaptureMetricsFn(...)\n\tC:/Users/bcart/go/pkg/mod/github.com/felixge/[email protected]/capture_metrics.go:39\ngithub.com/felixge/httpsnoop.CaptureMetrics(0xbda7e0, 0xc00009a0e0, 0xbdfe70, 0xc0000c80e0, 0xc0000c6200, 0x970ca7, 0xc0000aa2ec, 0x1e0edbf0778)\n\tC:/Users/bcart/go/pkg/mod/github.com/felixge/[email protected]/capture_metrics.go:28 +0xae\nmain.(*application).metrics.func1(0xbdfe70, 0xc0000c80e0, 0xc0000c6200)\n\tC:/Users/bcart/OneDrive/Desktop/Project/Project Z/Code/Parkit/cmd/api/middleware.go:223 +0x87\nnet/http.HandlerFunc.ServeHTTP(0xc0000aa0c0, 0xbdfe70, 0xc0000c80e0, 0xc0000c6200)\n\tC:/Program Files/Go/src/net/http/server.go:2069 +0x4b\nnet/http.serverHandler.ServeHTTP(0xc0000c8000, 0xbdfe70, 0xc0000c80e0, 0xc0000c6200)\n\tC:/Program Files/Go/src/net/http/server.go:2887 +0xaa\nnet/http.(*conn).serve(0xc00008a140, 0xbe0f20, 0xc0000aa240)\n\tC:/Program Files/Go/src/net/http/server.go:1952 +0x8cd\ncreated by net/http.(*Server).Serve\n\tC:/Program Files/Go/src/net/http/server.go:3013 +0x3b8\n"}
2022/07/28 14:40:19 http: superfluous response.WriteHeader call from github.com/felixge/httpsnoop.(*Metrics).CaptureMetrics.func1.1 (capture_metrics.go:53)

Does anyone know what is causing this error and how to resolve it?

You need to consult the driver that you are using. The database/sql package expects support for encoding/decoding only the basic types (string, int, etc.), plus the time.Time type, and perhaps a handful of others, but not more. Then the the driver that you are using can (doesn't have to) extend the support to additional, more complex types (e.g. lib/pq has array support but it's not automatic). And, finally, if neither the stdlib nor the driver support your type, you can opt to implement the sql.Scanner and driver.Valuer interfaces to add the support to a specific type yourself. - mkopriva