DataAction / AdoNetCore.AseClient

AdoNetCore.AseClient - a .NET Core DB Provider for SAP ASE
Apache License 2.0
106 stars 44 forks source link

AseConnection.InfoMessage is currently never triggered #78

Closed chgeuer closed 5 years ago

chgeuer commented 6 years ago

When I issue the following command to an ASE

LOAD DATABASE AZU WITH LISTONLY=LOAD_SQL,UNTIL_TIME='2018-08-16T17:40:23:310'

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 the private AseConnection.NotifyInfoMessage() method is never triggered... The event InfoMessageInternal has a TODO flag :-).

What would be the best / desired way to implement that functionality? InternalConnection.InternalExecuteAsync() looks like a place where the command.Connection could be passed down to the MessageTokenHandler to fire this event.

senseibaka commented 6 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:

chgeuer commented 6 years ago

Just for understanding: What is "the reference driver"? The Sybase.AdoNet4.AseClient NuGet package?

senseibaka commented 6 years ago

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.

chgeuer commented 6 years ago

Right. I just wanted to make sure you don't mean some other DLL burried deep down in the ase_setup.zip or so :-)

chgeuer commented 6 years ago

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
senseibaka commented 6 years ago

Yeah, it looks like it could be misbehaving. Do any of the other connection's events emit the other messages?

senseibaka commented 6 years ago

Or, does the AseInfoMessageEventArgs.Errors collection have the rest of the info messages, perhaps?

chgeuer commented 6 years ago

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.

senseibaka commented 6 years ago

Yes, as far as I can tell it's just a repackaging of the driver for easy access on nuget.

chgeuer commented 6 years ago

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...

senseibaka commented 6 years ago

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!

mholec commented 6 years ago

Hi Guys, just send me link to current version of the driver and I'll update NuGet package immediately.

senseibaka commented 6 years ago

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...

bbarry commented 6 years ago

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)

bbarry commented 6 years ago

the type AseInfoMessageEventArgs has an interesting interpretation of what it should be doing... it has 2 properties:

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) { }
}
senseibaka commented 6 years ago

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).

senseibaka commented 6 years ago

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

chgeuer commented 6 years ago

Hey Senseibaka, I have not yet started on that. So if you can build it, please go ahead and don't wait for me.

chgeuer commented 5 years ago

Very nice, works on my machine (TM). Thanks a lot!