Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bugfix: after executing statement table lock was not cleared #1195

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

letFunny
Copy link

@letFunny letFunny commented Oct 23, 2023

When we execute a statement, we step once and return. If the query returned more than one row, we would not have consumed everything so the underlying table would still be locked even when the execution had finished.

This change resets the statement after execution so that the locks can be cleared.

Apart from the included test, the following go program serves to reproduce the issue:

package main

import (
	"context"
	"database/sql"

	_ "github.com/mattn/go-sqlite3"
)

func main() {
	ctx := context.Background()
	sqldb, err := sql.Open("sqlite3", "file:test.db?cache=shared&mode=memory")

	stmt, err := sqldb.PrepareContext(ctx, "CREATE TABLE person (id number, name text);")
	stmt.Exec()
	stmt, err = sqldb.PrepareContext(ctx, "INSERT INTO person VALUES (1, 'Fred'), (2, 'Susan');")
	stmt.Exec()

	stmt, err = sqldb.PrepareContext(ctx, "SELECT name FROM person;")
	_, err = stmt.ExecContext(ctx)

	stmt, err = sqldb.Prepare("DROP TABLE person;")
	_, err = stmt.ExecContext(ctx)
	if err != nil {
		panic(err) // panic: database table is locked
	}
}

When we execute a statement, we step once and return. If the query
returned more than one row, we would not have consumed everything so the
underlying table would still be locked even when the execution had
finished.

This change resets the statement after execution so that the locks can
be cleared.
@@ -2083,6 +2083,12 @@ func (s *SQLiteStmt) execSync(args []driver.NamedValue) (driver.Result, error) {
return nil, err
}

rv = C.sqlite3_reset(s.s)
if rv != C.SQLITE_OK {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per the sqlite documentation (ref),

If the most recent call to sqlite3_step(S) for the prepared statement S indicated an error, then sqlite3_reset(S) returns an appropriate error code. The sqlite3_reset(S) interface might also return an error code if there were no prior errors but the process of resetting the prepared statement caused a new error. For example, if an INSERT statement with a RETURNING clause is only stepped one time, that one call to sqlite3_step(S) might return SQLITE_ROW but the overall statement might still fail and the sqlite3_reset(S) call might return SQLITE_BUSY if locking constraints prevent the database change from committing. Therefore, it is important that applications check the return code from sqlite3_reset(S) even if no prior call to sqlite3_step(S) indicated a problem.

that is why we consider the execution failed if reset returns an error code.

@letFunny
Copy link
Author

@mattn Can you take a look to see if this PR makes sense? Should I create an issue along with it?

@mattn
Copy link
Owner

mattn commented Jan 11, 2024

As far as I look this changes, looks good to me. @rittneje could you please take a look?

@letFunny
Copy link
Author

@rittneje can you take a look?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants