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
Get SQL Server Messages / PRINT command #221
Comments
Right now, not that I'm aware of. However, this is something I would personally like to enable in the future. |
Yes this would be nice to have. I'm not very familiar with SQL Server internals. Do you know of any resources that would help someone implement this feature? I found a StackOverflow answer that roughly describes how messages are transmitted, and apparently in .NET you're supposed to create a SqlInfoMessageEventHandler Delegate to handle the InfoMessage event of a SqlConnection. These don't help much to implement it yourself though. |
Oh, the driver already receives these messages. It is more of a question of |
Ah, got it. So we need a proposal for another extension to Edit: it looks like INFO messages are parsed in token.go#L590, and the answer to the above question is: no. :) |
I use the "log=63" in the connection string to print out the messages, not sure it's what you want? |
Please see the proposal on issue golang/go#19055 and the proposed CL https://golang.org/cl/39355 . This would allow dequeuing messages / print commands. Please comment as the the suitability of this API. |
This looks great thanks for linking it. |
@kardianos I don't know how I decided to revisit this out of the blue the same minute you updated issue 19055, seems like quite a coincidence. You say "I think this can be solved with messages.go if a driver supports it." Could you describe or show how this might look to the user? |
The |
This comment has been minimized.
This comment has been minimized.
@kardianos is there any effort underway to add the message support to go-mssqldb? If not I can take a stab at it to support my own project, as I don't really want to just inject a logger. The current logging code in the driver doesn't print the server provided messages anyway. |
@shueybubbles That would be great. It would need to be at the command level ideally anyway. If you wanted to stream messages, prior to receiving a result, you may be able to get away with smuggling in a messager interface through the context. Unfortunately, due to the design of database/sql, it makes retrieving messages inline with results difficult. So yes, please do try |
To summarize - I will try the model described in @kardianos 's earlier comment based on the |
What are those messages? I only know the INFO/ERROR messages, which already are in the logs.
I don't understand this part, as there's |
I'm looking to get the same messages for
I don't see such messages in the test logs, ie when running |
Oh, I see, thanks. |
There's another point I'd like to clarify: what should we do when we receive a non-fatal error? (severity 11 to 16) For example: RAISERROR ('Something went wrong, but please have a look at the following result set anyway', 11, 1)
SELECT 42 This currently only returns an error, while the server wants to send us a result set. In the following case, the error is returned through SELECT 41
RAISERROR ('Something went wrong, but please have a look at the following result set anyway', 11, 1)
SELECT 42 On a side note, I've had a look at how the .Net client handles info messages: it simply has a callback (a delegate). That seems similar to the simplistic implementation I've tried there: https://github.com/denisenkom/go-mssqldb/compare/master...tc-hib:pr-message-handler?expand=1 |
I'm fairly sure the driver should return both result sets and the error in between. I'm trying to replicate I think database changes etc are communicated as errors too, like error 5701 for changed database context. Maybe that's why the driver currently turns them off. Which brings up a new question - should the new behavior be opt-in? I'd expect so, for apps that aren't using the message loop would not want error 5701 breaking them |
No, these are INFO tokens, with error severity lower than 11. |
I think your callback model looks reasonable. I think it's kinda clunky to have to put it on every command though, when it would be scoped per connection for most apps. Would it be reasonable to also let an app provide the callback through the Connector? The per-Execute call could override the one in the Connector. |
I wouldn't mind as long as I can still define it per command. |
@kardianos do you expect these messages can be sent directly from For example, currently switch token := tok.(type) {
// By ignoring DONE token we effectively
// skip empty result-sets.
// This improves results in queries like that:
// set nocount on; select 1
// see TestIgnoreEmptyResults test
//case doneStruct:
//break loop
case []columnStruct:
cols = token
break loop
case doneStruct:
if token.isError() {
// need to cleanup cancellable context
cancel()
return nil, s.c.checkBadConn(token.getError(), false)
}
case ReturnStatus:
if reader.outs.returnStatus != nil {
*reader.outs.returnStatus = token
}
case Error:
}
} We definitely want to receive messages for print/insert/use etc as quickly as possible. I think there are several things to change about this particular method:
case tokenError:
err := parseError72(sess.buf)
if sess.logFlags&logDebug != 0 {
sess.log.Printf("got ERROR %d %s", err.Number, err.Message)
}
errs = append(errs, err)
if sess.logFlags&logErrors != 0 {
sess.log.Println(err.Message)
}
case tokenInfo:
info := parseInfo(sess.buf)
if sess.logFlags&logDebug != 0 {
sess.log.Printf("got INFO %d %s", info.Number, info.Message)
}
if sess.logFlags&logMessages != 0 {
sess.log.Println(info.Message)
}
|
Hmm I think |
Having spent a bit more time tinkering I think I need to pass the |
I think this code in sql.go is going to be problematic: https://github.com/golang/go/blob/6e738868a7a943d7d4fd6bb1963e7f6d78111726/src/database/sql/sql.go#L2978 It closes the I suppose the worst problem could be that intermediate messages between select statements are delayed, as the app will be waiting on Unless there's something I'm missing, @tc-hib 's proposal to use a plain synchronous logger func looks like the most reliable way to get messages and print them in the order received. We're very limited by how much reliance core has on |
Would it make sense for Rows to accumulate the messages instead of relying on any kind of queue or callback? I am not certain what other options have been discussed in the past for the core model. Whereas with the message queue model proposed in |
I can't have a look at the code now, as I'm on vacation. But, the more I think about it, the more I believe the callback function might be good enough after all. But we really have to decide how to handle errors that don't break the batch. We also have to decide if we want the callback function to receive other kinds of events, such as DONE with rowcount, or env changes. |
FWIW I can make this test pass. Obviously I need to write some more extensive tests and clean things up for any type of PR but it's a start. func TestMessageQueue(t *testing.T) {
conn := open(t)
defer conn.Close()
retmsg := &sqlexp.ReturnMessage{}
latency, _ := getLatency(t)
ctx, _ := context.WithTimeout(context.Background(), latency+200000*time.Millisecond)
rows, err := conn.QueryContext(ctx, "PRINT 'msg1'; select 100 as c; PRINT 'msg2'", retmsg)
if err != nil {
t.Fatal(err.Error())
}
defer rows.Close()
active := true
msgs := []interface{}{
sqlexp.MsgNotice{Message: "msg1"},
sqlexp.MsgNext{},
sqlexp.MsgRowsAffected{Count: 1},
sqlexp.MsgNotice{Message: "msg2"},
sqlexp.MsgNextResultSet{},
}
i := 0
rsCount := 0
for active {
msg := retmsg.Message(ctx)
if i >= len(msgs) {
t.Fatalf("Got extra message:%+v", msg)
}
t.Log(reflect.TypeOf(msg))
if reflect.TypeOf(msgs[i]) != reflect.TypeOf(msg) {
t.Fatalf("Out of order or incorrect message at %d. Actual: %+v. Expected: %+v", i, reflect.TypeOf(msg), reflect.TypeOf(msgs[i]))
}
switch m := msg.(type) {
case sqlexp.MsgNotice:
t.Log(m.Message)
case sqlexp.MsgNextResultSet:
active = rows.NextResultSet()
if active {
t.Fatal("NextResultSet returned true")
}
rsCount++
case sqlexp.MsgNext:
if !rows.Next() {
t.Fatal("rows.Next() returned false")
}
var c int
err = rows.Scan(&c)
if err != nil {
t.Fatalf("rows.Scan() failed: %s", err.Error())
}
if c != 100 {
t.Fatalf("query returned wrong value: %d", c)
}
}
i++
}
} The crux of the change is to split out some of the token handling to processSingleResponse notifies the client of a result set:
Then sql.go calls Columns so we scan for the columns: func (rc *Rows) Columns() (res []string) {
// in the message queue model, we know a column slice is in the channel because
// the client received sqlexp.MsgNext or sqlexp.MsgNextResultSet
if rc.reader.outs.msgq != nil {
scan:
for {
tok, err := rc.reader.nextToken()
if err == nil {
if tok == nil {
return []string{}
} else {
switch tokdata := tok.(type) {
case []columnStruct:
rc.cols = tokdata
break scan
}
}
}
}
}
res = make([]string, len(rc.cols))
for i, col := range rc.cols {
res[i] = col.ColName
}
return
} Then the app calls Next so Next has to wait for the rows: func (rc *Rows) Next(dest []driver.Value) error {
if !rc.stmt.c.connectionGood {
return driver.ErrBadConn
}
if rc.reader.outs.msgq != nil {
for {
tok, err := rc.reader.nextToken()
if err == nil {
if tok == nil {
return io.EOF
} else {
switch tokdata := tok.(type) {
case []interface{}:
for i := range dest {
dest[i] = tokdata[i]
}
return nil
case doneStruct:
if tokdata.Status&doneMore == 0 {
rc.requestDone = true
}
if tokdata.isError() {
e := rc.stmt.c.checkBadConn(tokdata.getError(), false)
switch e.(type) {
case Error:
// Ignore non-fatal server errors
default:
return e
}
}
return io.EOF
case ReturnStatus:
if rc.reader.outs.returnStatus != nil {
*rc.reader.outs.returnStatus = tokdata
}
}
}
} else {
return rc.stmt.c.checkBadConn(err, false)
}
}
}
... otherwise old behavior |
I'm finding several benefits to this "nonblocking" implementation that should be applicable to the non-message-loop based model.
My PR is going to approach the solution thusly:
|
I believe this is resolved by #690 for users of sqlexp. Thanks! |
SQL Server supports the PRINT command which prints out a string. This is then available to the client, e.g. in SSMS it appears in the Messages tab.
Does the driver offer any way to access these?
The text was updated successfully, but these errors were encountered: