Closed chgeuer closed 5 years ago
TODO flag
😄
Yeah you're right, MessageTokenHandler
should accept an AseConnection
(optional constructor argument?) and then call NotifyInfoMessage
. NotifyInfoMessage
will need to be changed from private
to internal
.
Also, have a look at what the reference driver does -- for instance:
InfoMessage
events?Errors
and the Message
properties on AseInfoMessageEventArgs
?AseErrorCollection
, I'm not sure what the answer is).Just for understanding: What is "the reference driver"? The Sybase.AdoNet4.AseClient
NuGet package?
Yep, the SAP/Sybase driver. I call it the "reference" driver because I use its exposed public interfaces (and behaviour inferred from tests) as a reference for writing this driver.
Right. I just wanted to make sure you don't mean some other DLL burried deep down in the ase_setup.zip or so :-)
I believe the reference driver (<package id="Sybase.AdoNet4.AseClient" version="1.0.0" targetFramework="net461" />
) does not behave correctly:
The SQL statement I run is this:
LOAD DATABASE AZU WITH LISTONLY=LOAD_SQL,UNTIL_TIME='2018-08-16T17:40:23:310'
Doing so on the isql
command line give me this:
1> LOAD DATABASE AZU WITH LISTONLY=LOAD_SQL,UNTIL_TIME='2018-08-16T17:40:23:310'
2> go
LOAD DATABASE AZU FROM '/tmp/ase-backup-20180816-082639-AZU-full-1-1.cdmp.pipe'
go
LOAD TRAN AZU FROM '/tmp/ase-backup-20180816-082727-AZU-tran-1-1.cdmp.pipe'
go
LOAD TRAN AZU FROM '/tmp/ase-backup-20180816-082734-AZU-tran-1-1.cdmp.pipe'
go
LOAD TRAN AZU FROM '/tmp/ase-backup-20180816-210028-AZU-tran-1-1.cdmp.pipe'
WITH UNTIL_TIME = 'Aug 16 2018 5:40:23:310PM'
go
Running the same via the reference driver
namespace FullDotNetApp
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Sybase.Data.AseClient; // reference driver
class Program
{
static async Task Main(string[] args)
{
var server_address = "ase1";
var server_port = 5000;
var database = "AZU";
var user = "sapsa";
var password = "Test123.-";
var cts = new CancellationTokenSource();
using (var connection = new AseConnection(connectionString: $"Data Source={server_address};Server Port={server_port};Database={database};UserID={user};Password={password}"))
{
await connection.OpenAsync(cts.Token);
var result = new List<string>();
void captureResult(object sender, AseInfoMessageEventArgs aseInfoMessageEventArgs)
{
result.Add(aseInfoMessageEventArgs.Message);
}
connection.InfoMessage += captureResult;
using (var command = connection.CreateCommand())
{
command.CommandText = "LOAD DATABASE AZU WITH LISTONLY=LOAD_SQL,UNTIL_TIME='2018-08-16T17:40:23:310'";
await command.ExecuteNonQueryAsync(cts.Token);
}
connection.InfoMessage -= captureResult;
foreach (var line in result)
{
await Console.Out.WriteLineAsync(line);
}
}
}
}
}
only gives this:
LOAD DATABASE AZU FROM '/tmp/ase-backup-20180816-082639-AZU-full-1-1.cdmp.pipe'\n
Yeah, it looks like it could be misbehaving. Do any of the other connection's events emit the other messages?
Or, does the AseInfoMessageEventArgs.Errors
collection have the rest of the info messages, perhaps?
I need to ask an additional question re: the term "reference driver": Looking at the package info on Nuget, the package comes from https://www.nuget.org/profiles/mholec which seems equivalent to @mholec on [github](https://github.com/mholec]. The Sybase.AdoNet4.AseClient package info page lists SAP as author, but to me it seems like an SAP-external project.
Yes, as far as I can tell it's just a repackaging of the driver for easy access on nuget.
Ah, OK, so you say the underlying binary is actually from SAP, it's just that the NuGet package came via community... Got it.
I funneled back feedback to SAP that the driver behaves in a way that it only returns the first line...
Yep. FYI the binary's file version is 4.157.104.0
, which is admittedly a little old. So there's a chance your issue's already fixed in a newer version.
It might be a good idea to ask mholec to update the nuget package to a newer version!
Hi Guys, just send me link to current version of the driver and I'll update NuGet package immediately.
Hi @mholec I'm struggling to find a direct link.
Apparently it comes packaged with SDK for Adaptive Server Enterprise
, as well as the database server itself...
Our reference driver places all messages inside the AseInfoMessageEventArgs.Errors
property... adapted from my production logging code:
void captureResult(object sender, AseInfoMessageEventArgs aseInfoMessageEventArgs) {
foreach (AseError error in aseInfoMessageEventArgs.Errors) {
result.Add("\nMessage: " + error.Message +
"\nErr Number: " + error.MessageNumber +
"\nSQLState: " + error.SqlState +
"\nSeverity: " + error.Severity +
"\nIsError: " + error.IsError +
"\nIsFromServer: " + error.IsFromServer +
"\nIsFromClient: " + error.IsFromClient +
"\nIsInformation: " + error.IsInformation +
"\nIsWarning: " + error.IsWarning +
"\nLineNum: " + error.LineNum +
"\nProcName: " + error.ProcName +
"\nServerName: " + error.ServerName +
"\nStatus: " + error.Status +
"\nTranState: " + error.TranState +
"\n----");
}
}
outputs multiple lines:
Message: LOAD DATABASE [redacted]
Err Number: 3640
SQLState: ZZZZZ
Severity: 10
IsError: False
IsFromServer: True
IsFromClient: False
IsInformation: False
IsWarning: True
LineNum: 1
ProcName:
ServerName: [redacted]
Status: 0
TranState: 1
----
(I am using the 16.0.3.0 driver; to get the latest you are supposed to have your dba log in to their portal and download the sdk)
the type AseInfoMessageEventArgs
has an interesting interpretation of what it should be doing... it has 2 properties:
AseInfoMessageEventArgs.Errors
is never null
, it may contain 0 results though. It contains all? the TDS_EED
messages from the response stream in the sequence they arrive.AseInfoMessageEventArgs.Message
always returns the value of Errors[0].Message
and thus may potentially throw an index out of bounds error (in practice I don't think this ever happens because I hope the driver only ever raises the event when Errors
has at least one element; regardless you should not treat .Message
as a safe property to rely on)My suggestion for the classes would be something like
public class AseInfoMessageEventArgs : EventArgs
{
[NotNull]
public AseErrorCollection Errors { get; }
public string Message => Errors.FirstOrDefault()?.Message;
internal AseInfoMessageEventArgs(AseErrorCollection errors) => Errors = errors;
}
public class AseErrorCollection : ReadOnlyCollection<AseError>
{
internal AseErrorCollection([NotNull][ItemNotNull] IList<AseError> list) : base(list) { }
}
Now that the reference driver nuget package is marked as unlisted, I will probably update the test suite to require a BYO driver instead (and update wiki to explain how to get it).
Hi @chgeuer, it turns out that my company (DA) also has some need for this driver to report server messages.
Are you still active on this issue?
Thanks
Hey Senseibaka, I have not yet started on that. So if you can build it, please go ahead and don't wait for me.
Very nice, works on my machine (TM). Thanks a lot!
When I issue the following command to an ASE
it returns a SQL script which I could use to restore a database to a particular point in time. The problem is that the response is not a result set or so, but just informational messages.
I subscribed to
AseConnection.InfoMessage
, but theprivate AseConnection.NotifyInfoMessage()
method is never triggered... The eventInfoMessageInternal
has a TODO flag :-).What would be the best / desired way to implement that functionality?
InternalConnection.InternalExecuteAsync()
looks like a place where thecommand.Connection
could be passed down to theMessageTokenHandler
to fire this event.