-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
database/sql: add support for returning multiple result sets #12382
Comments
I might be missing something but why not to call |
@kostya-sh In the simple example I gave up top, yes, that would be fine. In every case you could return results and and re-query the next segment. In larger queries that have many steps and many queries in them that rely on the data passed to them this quickly becomes sub-optimal, possibly a deal breaker for large datasets where you don't want to send intermediate result sets back and forth. Again you can work around that with global or session temp tables, but again that isn't always optimal. |
@kardianos, thank you for the clarification. I have never used multiple return results myself but I agree that there are some rare cases when they could be useful. Just for the reference the JDBC API supports multiple result sets via execute() and getMoreResults(). |
Another motivating examples are stored procedures. Those are often provided to keep the API for complex operations stable, while allowing changes to the actual operation that is performed. See go-sql-driver/mysql#66 for how cumbersome using stored procedures actually is in real world cases with Go and |
The lack of an API in |
I have an alternate API that I'm using that works when tables are larger and queries more complex. The implementation is not the best but the API is roughly what I need: https://bitbucket.org/kardianos/rdb/src . |
Agreed. I wanna say a lot of procedures return multiple result sets, and we can't modify them because they are part of existing systems. |
Just curious, since the milestone is set to unplanned, does this mean it probably won't get implemented for a long time? /cc @bradfitz |
The unplanned milestone means that nobody plans to work on it. That could mean that it will never get implemented. Or, if somebody decides to work on it, it could get implemented in the next release. Go is an open source project, anybody could fix this, even you. |
So just thinking about how this would work. Does something like the following make sense, minus the implementation details?
usage example:
|
An alternative, more minimal option maybe? // NextResultSet returns the next result set from the query
// NextResultSet always returns a non-nil value.
// Errors are deferred until *Rows.Scan method is called.
func (r *Rows) NextResultSet() *Rows Example: func DoQuery() error {
rows, err := db.Query("query with 2 result sets")
if err != nil {
return err
}
defer rows.Close()
// Handle result set 1
for rows.Next() {
err := rows.Scan(...)
}
if err := rows.Err(); err != nil {
return err
}
rows = rows.NextResultSet()
defer rows.Close()
for rows.Next() {
err := rows.Scan(...)
if err == sql.ErrNoRows {
//Application error
}
}
return rows.Err() This is not perfect, and I'm not even sure it's possible, but it looks like it could be as Rows owns the connection. |
@freeformz Yes, I think However, I think the signature would be @bradfitz Opinion on this signature? |
@kardianos FWIW: |
I was thinking something like this. But I also tend to push alot to frameworks too.
|
So... // NextResultSet prepares *Rows for the next result set and returns true.
// If there are no more result sets it returns false.
func (r *Rows) NextResultSet() bool But you probably don't want to use an enclosing func DoQuery() error {
rows, err := db.Query("query with 2 result sets")
if err != nil {
return err
}
defer rows.Close()
// Handle result set 1
for rows.Next() {
if err := rows.Scan(&structTypeA); err != nil {
return err
}
...
}
if err := rows.Err(); err != nil {
return err
}
if !rows.NextResultSet() {
panic("Expected more rows!")
}
for rows.Next() {
if err := rows.Scan(&structTypeB); err != nil {
return err
}
...
}
if err := rows.Err(); err != nil {
return err
}
if rows.NextResultSet() {
panic("Didn't export more rows!")
}
} |
Yeah, my example could be how you generically buffer all the result sets up before you deal with them. Your example would be how you might use it interactively. The downside is that It would work. I think it would be better then |
I would expect any error to cause NextResultSet() to return false, so yeah. if !rows.NextResultSet() {
return errors.wrap(rows.Err(), "expected another result set")
} |
If NextResultSet returns bool and error only, then how will we get the next result set? |
By calling Result.Next(). Internally, multiple result sets come linearly off the wire, one after another. 1 query results in N sequential result sets. |
My coworker is proposing a stored procedure API that works akin to CREATE PROCEDURE GetThing(thingID INTEGER, getRelatedThing1 BOOLEAN, getRelatedThing2 BOOLEAN) where the result set can have one to three sets of rows. How about something like this? func GetThing(t *sql.Tx, thingID int, getRelatedThing1 bool, getRelatedThing2 bool) (t *Thing, r1 *Related, r2 *Related2, err error) {
r, err := t.Query("CALL GetThing(?, ?, ?);", thingID, getRelatedThing1, getRelatedThing2)
if err != nil { return nil, nil, nil err }
defer r.Close()
for r.Next() {
// r.Scan() into t
}
if err := r.Err(); err != nil { return nil, nil, nil, err }
if getRelatedThing1 {
for r.Next() {
// r.Scan() into r1
}
if err := r.Err(); err != nil { return nil, nil, nil, err }
}
if getRelatedThing2 {
for r.Next() {
// r.Scan() into r2
}
if err := r.Err(); err != nil { return nil, nil, nil, err }
}
return
} Or should the if getRelatedThing1 {
if err := r.NextSet(); err != nil { return nil, nil, nil, err }
for r.Next() { ? |
@andlabs I don't understand what you are trying to do. Please ensure you have read and understood the example in #12382 (comment) . |
Hm, yeah; reading it again it seems mostly equivalent to what I have anyway. My question is is the true // result set one - three rows
true
true
false // end of result set one
true // result set two - two rows
true
false // end of result set two
false // result set three - no rows
true // result set four - one row
false // end of result set four
false // no more result sets; return false forever
false
.
.
. If one of the result sets has no rows, then it'd just return false twice in a row. (This should not have any problems because of how If func (r *Rows) NextResultSet() (err error) If the result set is empty, My reasoning for this is that since you know how many result sets the SQL query should return (and if you don't, why are you making a random query?), and you can assume there is no bug in the driver package, the only scenario for which there would be no more result sets early is if some error occurred; for example, a network connection problem. The final check in the linked comment is also not necessary for the same reason; once you have read the last row of what you already know is the last result set, you can safely just (Of course, with that thought process, we can conclude that One additional question: how will stored procedure that issue statements that return a |
I agree with @andlabs , implicit calling When a result set had been read to And methods of And we could use extra arguments in As for interleaved multiple result sets fetching support like MSSQL's MARS, as @kardianos pointed out, it is uncommon, so I think it could be left out. And I think true session mutiplexing like Oracle supports is better, which could also fits in here. |
@andlabs @noblehng I don't think that would be (1) backwards compatible and (2) you are creating an ad-hoc API that does two different things. If a current code base calls Next() even after it returns false, it could still work, even if non-optimal. However, with this suggestion it would break. I will grant you it could be considered a bug in the usage. I really think we are doing two logical things. I agree just using Next() is possible, I think it is just less optimal. I think output parameters support should be included in the named parameter discussion. Right, Ii don't want to support MARS or Oracle session multiplexing. I have yet to encounter any code in any organization I've encountered use these features. I just want to support sequential multiple result sets. |
In that case what do you think of what I said about |
@kardianos It wouldn't break current code bases, because current code bases are all dealing with single result set, so calling I just think the underlying
func (rs *Rows) NextResultSet() error {
if !rs.closed {
return ErrProcessing
}
if err := rs.rowsi.Next(nil); err != nil {
return err
}
rs.lasterr, rs.closed = nil, false
return nil
} |
The meaning of Currently, Now to implement of So I think it would be better adding a new interface to type ResultSetsIter interface {
CloseResultSet() error
NextResultSet() error
} And in func (rs *Rows) NextResultSet() error {
if err := rs.lasterr; err != nil && err != io.EOF {
return err
}
if !rs.resultsetClosed {
return ErrProcessing
}
next, ok := rs.rowsi.(driver.ResultSetsIter)
if !ok {
return ErrNotSupport
}
if err := next.NextResultSet(); err != nil {
return err
}
rs.resultsetClosed = false
return nil
}
func (rs *Rows) CloseResultSet() error {
if rs.resultsetClosed {
return nil
}
rs.resultsetClosed = true
next, ok := rs.rowsi.(driver.ResultSetsIter)
if !ok {
return nil
}
return next.CloseResultSet()
} Then modify |
@noblehng I'm unsure at this time what the driver interface would look like, you could be correct there. Unsure. |
@kardianos the |
CL https://golang.org/cl/30592 mentions this issue. |
Many databases support returning multiple result sets. RDBMS that support this include MySQL, Postgresql, MS SQL Server, Oracle.
An example query that demonstrates this is:
The above would be expected to return two data sets, each with their own arity.
The above example is simple to see the effects, but is not motivating. A more motivating example usually includes some degree of procedural logic where multiple arities should be preserved and returned separately.
Example (1) Select data to form a matrix on the client:
Example (2) make an update, but return the updated data to the client so it can update its UI.
The text was updated successfully, but these errors were encountered: