Go sql row scan inside loop

Hello there :wave:,
I am fairly new to the go programming language and this is my first post in this forum which was recommended to me from an online course. The year 2022 is still pretty fresh so happy new year to @all.

I am struggling to understand why I cannot return an sql row inside of a loop? Or I am not understanding how to do it properly?

I am trying to create a column in a PostgreSQL table and return the whole column row afterwards to print it out. I am trying to do that inside of a loop (which does not work), and whenever I do it outside of the loop it does work. Please enlighten me with your wisdom :pray:.

DB:

go-mvc=# select * from messages;
 id  | name  |     message     |         created_at
-----+-------+-----------------+----------------------------
 101 | Ori   | Skol!           | 2022-01-23 19:43:14.761144
 102 | Oin   | Raise the axes! | 2022-01-23 19:43:14.763818
 103 | Gloin | Bouncing around | 2022-01-23 19:43:14.764389
(3 rows)

Controller:

func CreateMessageProcessJSON(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		w.WriteHeader(405)
		return
	}
	if r.Header.Get("content-type") != "application/json" {
		w.WriteHeader(406)
		return
	}

	msgs := []models.MessageJSON{}
	helpers.ParseBody(r, &msgs)

	// DOES NOT WORK
	// for _, msg := range msgs {
	// 	if err := msg.CreateFromJSON(); err != nil {
	// 		http.Error(w, http.StatusText(500), http.StatusInternalServerError)
	// 		return
	// 	}
	// }

	// WORKS
	if err := msgs[0].CreateFromJSON(); err != nil {
		http.Error(w, http.StatusText(500), http.StatusInternalServerError)
		return
	}

	pd := models.PageDataJSON{
		msgs,
		models.RawData{Title: "Show json message"},
	}

	fmt.Fprintln(w, pd)
}

Model:

type MessageJSON struct {
	Id        int64     `json:"id"`
	Name      string    `json:"name"`
	Message   string    `json:"msg"`
	CreatedAt time.Time `json:"created_at"`
}

func (msg *MessageJSON) CreateFromJSON() (err error) {
	// validate form values
	if msg.Name == "" || msg.Message == "" {
		errors.New("this error just triggers another error")
		return
	}

	statement := `INSERT INTO messages (name, message, created_at) VALUES ($1, $2, $3) RETURNING id, name, message, created_at`

	stmt, err := db.Db.Prepare(statement)
	if err != nil {
		return
	}

	// use QueryRow to return a row and scan the returned id into the User struct
	err = stmt.QueryRow(msg.Name, msg.Message, time.Now()).Scan(&msg.Id, &msg.Name, &msg.Message, &msg.CreatedAt)
	return
}

The column gets created every time I make a post request, but the scan does not seem to make any impact on my struct.

Post request with loop:

% curl -X POST \
http://localhost:8080/messages/createjson/process \
-H "Content-Type: application/json" \
-d '[{"name":"Ori", "msg":"Skol!"},{"name":"Oin","msg":"Raise the axes!"},{"name":"Gloin","msg":"Bouncing around"}]' -v

Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying ::1:8080...
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /messages/createjson/process HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 111
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sun, 23 Jan 2022 18:43:14 GMT
< Content-Length: 178
< Content-Type: text/plain; charset=utf-8
<
{[{0 Ori Skol! 0001-01-01 00:00:00 +0000 UTC} {0 Oin Raise the axes! 0001-01-01 00:00:00 +0000 UTC} {0 Gloin Bouncing around 0001-01-01 00:00:00 +0000 UTC}] {Show json message}}
* Connection #0 to host localhost left intact

Post request without loop (will give me the correct ID but is not what I want):

% curl -X POST \
http://localhost:8080/messages/createjson/process \
-H "Content-Type: application/json" \
-d '[{"name":"ID?", "msg":"CHEERS!!!"}]' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying ::1:8080...
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /messages/createjson/process HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 35
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sun, 23 Jan 2022 18:52:18 GMT
< Content-Length: 83
< Content-Type: text/plain; charset=utf-8
<
{[{105 ID? CHEERS!!! 2022-01-23 19:52:18.941026 +0000 +0000}] {Show json message}}
* Connection #0 to host localhost left intact

Take care,
Luke

Hi @lmllr,

This is a classic Go caveat that is easy to overlook if you’re not used to it.

In this loop expression, msg is a copy of the corresponding array element.

To get the result from CreateFromJSON() into the array, try this:

for i := range msgs {
	if err := msgs[i].CreateFromJSON();
4 Likes

Thank you so much @christophberger, not only this solved my problem but was a major aha moment, too! :tada:

2 Likes

Thanks for your feedback @lmllr, glad I could be of help.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.