0

Im a newbie in go and not the best in sql.

I have a simple Table in my Database with the name of users. I store the SAM, First Name and Last Name in the table. When i now try to change something in the database, i get the error database is locked. Thats my code:

func createNewUser(w http.ResponseWriter, r *http.Request) {
    var user User
    err := decodeJSONBody(w, r, &user)
    if checkError(w, err) {
        return
    }
    rows, err := mainDB.Query("SELECT * FROM users WHERE SAM = ?", user.Sam)
    if checkError(w, err) {
        return
    }
    defer rows.Close()
    if rows.Next() {
        http.Error(w, "User already exists", http.StatusConflict)
        return
    }
    _, err = mainDB.Exec("INSERT INTO users (SAM, Vorname, Nachname) VALUES (?, ?, ?)", user.Sam, user.Vorname, user.Nachname)
    if checkError(w, err) {
        return
    }
    json.NewEncoder(w).Encode(user)
}

decodeJSONBody and checkError work and have nothing to do with the database. And as far as I've learned, rows.Close should close the columns so that I can write something back in

Bαmbus
  • 61
  • 1
  • 1
  • 10
  • Which dbms are you using? – jarlh Nov 23 '21 at 11:46
  • Post the actual log data – Margach Chris Nov 23 '21 at 11:48
  • @jarlh I'm using the `database/sql` library with sqlite3 – Bαmbus Nov 23 '21 at 12:03
  • @MargachChris with panic im getting this Output: ```http: panic serving [::1]:58284: database is locked``` – Bαmbus Nov 23 '21 at 12:05
  • Does this answer your question? [How do I unlock a SQLite database?](https://stackoverflow.com/questions/151026/how-do-i-unlock-a-sqlite-database) – erik258 Nov 23 '21 at 16:58
  • 1
    "rows.Close should close the columns" - correct but as you `defer` this `rows.Close()` runs when the function exits (so after the `INSERT`); not an issue with most databases but could be with SQLite. Try adding a `rows.Close()` before the `INSERT` (it's safe to call `rows.Close()` multiple times). Please show how you are opening the database (see "Error: database is locked" in the [FAQ](https://github.com/mattn/go-sqlite3#faq)) - a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) would help. – Brits Nov 23 '21 at 20:43
  • @Brits I added `rows.Close()` multiple times, and now it works. thank you so much. if you create an answer, i can mark it as right – Bαmbus Nov 24 '21 at 09:07

1 Answers1

0

As per the comments SQLite has some limitations around locking/concurrency which means you need to take care when running multiple statements concurrently. Unfortunately I had not reviewed your code in detail when posting my comment so, despite seemingly solving the issue, it was in error.

You had added a defer rows.Close(); this will free up the database connection used to run the query but, due to the defer, this will only happen when the surrounding function returns. Normally this is not a big issue because looping through a result set in its entirety automatically closes the rows. The documentation states:

If Next is called and returns false and there are no further result sets, the Rows are closed automatically and it will suffice to check the result of Err.

In your code you do return if rows.Next() is true:

if rows.Next() {
    http.Error(w, "User already exists", http.StatusConflict)
    return
}

This means that adding an extra rows.Close() should not be needed. However as you say "added rows.Close() multiple times, and now it works" I suspect that your full code may have been a bit more complicated than that presented (and one of the added rows.Close() was needed).

So adding extra calls to rows.Close() should not be needed; it will not cause an issue (other than an unnecessary function call). However you should check for errors:

rows, err := mainDB.Query("SELECT * FROM users WHERE SAM = ?", user.Sam)
if checkError(w, err) {
    rows.Close()
    return
}
if rows.Next() {
    http.Error(w, "User already exists", http.StatusConflict)
    return
}
if err = rows.Err(); err != nil {
   return // It's worth checking fort an error here
}

Note that the FAQ for go-sqlite3 includes information on dealing with "Error: database is locked" (and it's worth ensuring you follow the recommendations).

Note2: Consider using EXISTS instead of running the query and then attempting to fetch a row - it is likely to be faster and allows you to use QueryRow which simplifies your code.

Brits
  • 14,829
  • 2
  • 18
  • 31