diff --git a/multi_result_set_test.go b/multi_result_set_test.go new file mode 100644 index 0000000..efeb31e --- /dev/null +++ b/multi_result_set_test.go @@ -0,0 +1,111 @@ +package sqlx + +import "testing" + +// Scanning a second result set with a different column layout must not reuse +// the first set's field cache (upstream #380, #857; test from upstream #747). +func TestMultiResultSet(t *testing.T) { + RunWithSchema(defaultSchema, t, func(db *DB, t *testing.T, now string) { + var sp, sql string + + loadDefaultFixture(db, t) + + switch db.DriverName() { + case "mysql": + sp = ` + CREATE PROCEDURE multiQuery() + BEGIN + SELECT first_name, last_name, email FROM person LIMIT 1; + SELECT country, telcode FROM place LIMIT 1; + END + ` + sql = "CALL multiQuery();" + case "postgres": + sql = ` + SELECT first_name, last_name, email FROM person LIMIT 1; + SELECT country, telcode FROM place LIMIT 1; + ` + default: + // sqlite3 does not support multiple result sets. + return + } + + type Person struct { + FirstName string `db:"first_name"` + LastName string `db:"last_name"` + Email string `db:"email"` + } + type Place struct { + Country string `db:"country"` + TelCode string `db:"telcode"` + } + + if sp != "" { + if _, err := db.Exec("DROP PROCEDURE IF EXISTS multiQuery"); err != nil { + t.Fatal(err) + } + + if _, err := db.Exec(sp); err != nil { + t.Fatal(err) + } + } + + rows, err := db.Queryx(sql) + if err != nil { + t.Fatal(err) + } + defer rows.Close() + + var ( + person Person + place Place + ) + + var i int + for rows.Next() { + i++ + if err = rows.StructScan(&person); err != nil { + t.Fatal(err) + } + } + if i < 1 { + t.Errorf("[%s] Expected at least one record, got none", db.DriverName()) + } + + if !rows.NextResultSet() { + t.Fatalf("[%s] Expected a second recordset, got one", db.DriverName()) + } + + i = 0 + for rows.Next() { + i++ + if err = rows.StructScan(&place); err != nil { + t.Fatal(err) + } + } + if i < 1 { + t.Errorf("Expected at least one records, got none") + } + + // nest no next + rows2, err := db.Queryx("SELECT first_name, last_name, email FROM person LIMIT 2;") + if err != nil { + t.Fatal(err) + } + defer rows2.Close() + + i = 0 + for rows2.Next() { + i++ + if err = rows2.StructScan(&person); err != nil { + t.Fatal(err) + } + } + if i < 2 { + t.Errorf("[%s] Expected at least 2 records, got %d", db.DriverName(), i) + } + if rows.NextResultSet() { + t.Fatalf("[%s] Did not expected a second recordset", db.DriverName()) + } + }) +} diff --git a/sqlx.go b/sqlx.go index 7435e48..67ff8bf 100644 --- a/sqlx.go +++ b/sqlx.go @@ -636,6 +636,17 @@ func (r *Rows) StructScan(dest interface{}) error { return r.Err() } +// NextResultSet behaves like sql.NextResultSet, but cleans cache of previous result set. +func (r *Rows) NextResultSet() bool { + if !r.Rows.NextResultSet() { + return false + } + r.started = false + r.fields = nil + r.values = nil + return true +} + // Connect to a database and verify with a ping. func Connect(driverName, dataSourceName string) (*DB, error) { db, err := Open(driverName, dataSourceName)