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

Using with goroutines #285

Open
ceyhunkerti opened this issue Feb 8, 2022 · 3 comments
Open

Using with goroutines #285

ceyhunkerti opened this issue Feb 8, 2022 · 3 comments
Assignees
Labels

Comments

@ceyhunkerti
Copy link

ceyhunkerti commented Feb 8, 2022

Hi, having trouble with using go routines.

sqlmock version 1.5.0

below is the code when functions run serial no error occurs but panics when used with goroutines.

🖊️ update:
second script works, which does not use prepared statements

package main

import (
	"database/sql"
	"sync"

	"github.com/DATA-DOG/go-sqlmock"
)

func main() {
	db, mock, _ := sqlmock.New()

	mock.MatchExpectationsInOrder(false)
	data := []interface{}{
		1,
		"John Doe",
		2,
		"Jane Doe",
	}
	rows := sqlmock.NewRows([]string{"id", "name"})
	rows.AddRow(data[0], data[1])
	rows.AddRow(data[2], data[3])

	mock.ExpectExec("DROP TABLE IF EXISTS test_out").WillReturnResult(sqlmock.NewResult(0, 0))
	mock.ExpectExec("TRUNCATE TABLE").WillReturnResult(sqlmock.NewResult(0, 0))

	mock.ExpectExec("CREATE TABLE IF NOT EXISTS test_out").WillReturnResult(sqlmock.NewResult(0, 0))

	mock.ExpectQuery("select").WillReturnRows(rows).WithArgs()

	mock.ExpectPrepare("INSERT INTO").
		ExpectExec().
		WithArgs(
			data[0],
			data[1],
			data[2],
			data[3],
		).
		WillReturnResult(sqlmock.NewResult(0, 2))

	funcs := []func(db *sql.DB) error{
		runSelect,
		runCreate,
		runInsert,
		runDrop,
		runTruncate,
	} 
	// // this works
	// for _, f := range funcs {
	// 	if err := f(db); err != nil {
	// 		panic(err)
	// 	}
	// }

	//! this does not work
	wg := &sync.WaitGroup{}
	for _, f := range funcs {
		wg.Add(1)
		go func(f func(db *sql.DB) error, wg *sync.WaitGroup) {
			defer wg.Done()
			if err := f(db); err != nil {
				panic(err)
			}
		}(f, wg)
	}
	wg.Wait()
}

func runSelect(db *sql.DB) error {
	_, err := db.Query("select * from test_out")
	return err
}

func runCreate(db *sql.DB) error {
	_, err := db.Exec("CREATE TABLE IF NOT EXISTS test_out (a varchar(255)")
	return err
}

func runInsert(db *sql.DB) error {
	stmt, err := db.Prepare("INSERT INTO test_out (id,full_name) VALUES (?,?),(?,?)")
	if err != nil {
		return err
	}
	_, err = stmt.Exec(1, "John Doe", 2, "Jane Doe")
	return err
}

func runDrop(db *sql.DB) error {
	_, err := db.Exec("DROP TABLE IF EXISTS test_out")
	return err
}

func runTruncate(db *sql.DB) error {
	_, err := db.Exec("TRUNCATE TABLE test_out")
	return err
}
package main

import (
	"database/sql"
	"sync"

	"github.com/DATA-DOG/go-sqlmock"
)

func main() {
	db, mock, _ := sqlmock.New()

	mock.MatchExpectationsInOrder(false)
	data := []interface{}{
		1,
		"John Doe",
		2,
		"Jane Doe",
	}
	rows := sqlmock.NewRows([]string{"id", "name"})
	rows.AddRow(data[0], data[1])
	rows.AddRow(data[2], data[3])

	mock.ExpectExec("DROP TABLE IF EXISTS test_out").WillReturnResult(sqlmock.NewResult(0, 0))
	mock.ExpectExec("TRUNCATE TABLE").WillReturnResult(sqlmock.NewResult(0, 0))

	mock.ExpectExec("CREATE TABLE IF NOT EXISTS test_out").WillReturnResult(sqlmock.NewResult(0, 0))

	mock.ExpectQuery("select").WillReturnRows(rows).WithArgs()

	// mock.ExpectPrepare("INSERT INTO").
	// 	ExpectExec().
	// 	WithArgs(
	// 		data[0],
	// 		data[1],
	// 		data[2],
	// 		data[3],
	// 	).
	// 	WillReturnResult(sqlmock.NewResult(0, 2))

	mock.
		ExpectExec("INSERT INTO").
		WithArgs(
			data[0],
			data[1],
			data[2],
			data[3],
		).
		WillReturnResult(sqlmock.NewResult(0, 2))

	funcs := []func(db *sql.DB) error{
		runSelect,
		runCreate,
		runInsert,
		runDrop,
		runTruncate,
	}

	// println("Testing")
	// for _, f := range funcs {
	// 	if err := f(db); err != nil {
	// 		panic(err)
	// 	}
	// }
	println("Testing with go routines")
	wg := &sync.WaitGroup{}
	for _, f := range funcs {
		wg.Add(1)
		go func(f func(db *sql.DB) error, wg *sync.WaitGroup) {
			defer wg.Done()
			if err := f(db); err != nil {
				panic(err)
			}
		}(f, wg)
	}
	wg.Wait()
}

func runSelect(db *sql.DB) error {
	_, err := db.Query("select * from test_out")
	return err
}

func runCreate(db *sql.DB) error {
	_, err := db.Exec("CREATE TABLE IF NOT EXISTS test_out (a varchar(255)")
	return err
}

func runInsert(db *sql.DB) error {
	_, err := db.Exec("INSERT INTO test_out (id,full_name) VALUES (?,?),(?,?)",
		1, "John Doe", 2, "Jane Doe",
	)

	// stmt, err := db.Prepare("INSERT INTO test_out (id,full_name) VALUES (?,?),(?,?)")
	// if err != nil {
	// 	return err
	// }
	// _, err = stmt.Exec(1, "John Doe", 2, "Jane Doe")
	return err
}

func runDrop(db *sql.DB) error {
	_, err := db.Exec("DROP TABLE IF EXISTS test_out")
	return err
}

func runTruncate(db *sql.DB) error {
	_, err := db.Exec("TRUNCATE TABLE test_out")
	return err
}
@JessieAMorris
Copy link
Collaborator

I've been able to reproduce the issue (it is sometimes inconsistent but happens often enough). I've added a test case on a branch and am going to dig into the issue further over the next few days.

#288

@JessieAMorris
Copy link
Collaborator

The issue seems to be related to how Golang handles prepared statements. From the docs:

When the Stmt needs to execute on a new underlying connection, it will prepare itself on the new connection automatically.

At this point I'm thinking about adding a flag similar to mock.MatchExpectationsInOrder(bool) but that would allow prepared statements to be prepared multiple times.

@siddharth178
Copy link

siddharth178 commented Feb 11, 2023

Tried to fix it, but with my limited knowledge of codebase. Please give it a look.
#309

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

No branches or pull requests

4 participants