7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#Advent Calendar 2024

Day 21

Pure C# で作るODBC ドライバー

Last updated at Posted at 2024-12-20

はじめに

私は普段、データベース関係の製品を触っていて、様々なアプリやツールなどからアクセスする際に、ドライバーを使い分けています。

その中でも、汎用的なものとして、JDBC とODBC は、特定のアプリに依存せず、またOS もWindows, Linux, Mac と幅広い環境で使用できる技術です。

ODBC ドライバーを使ったことのある人の99割1は、「ODBC ドライバーって作れるのかな?」と一度くらいは思ったことがあるはずです。

今回は、そんな中でODBC ドライバーを自作してみよう、しかも、Native AOT を使って、C# だけで作ってみようというネタです。

目標は、Windows で使えるSQL クライアントツールのA:5 SQL Mk-2 からODBC ドライバーに接続して、SQL を実行し、結果を返してくれるところまでとしました。

いわば、ODBC ドライバーの「Hello, World!」みたいなものです。

image.png

使用ツール

Microsoft Visual Studio Community 2022
A5:SQL Mk-2
Depencencies
SQLite ODBC Driver

ODBC2 とは

ODBC は、簡単に言うと、データにアクセスするための共通インタフェース (API) です。
ODBC というと、データベース(RDBMS) への接続などを想像することが多いですが、接続先はデータベースに限らず、Excel やCSV といったファイルだったり、Web API を使用してSaaS のデータにアクセスできるものもあります。

Native AOT3 とは

C# で、C やC++ などと同じように、ネイティブコードのプログラムを生成する仕組みです。
通常、C# で作られたプログラムは、MSIL という中間言語の形式になっていて、プログラムが実行されるときにランタイムによって、ネイティブコードに変換されて実行されます。

この仕組みは、ランタイムさえ対応していればOS やCPU に依存せず、同じバイナリを実行できたり、ランタイムの性能が向上して、より効率的なネイティブコードが生成できるようになると、プログラムを変更しなくてもパフォーマンスが向上するなどのメリットがあります。

一方、デメリットとしては、iOS のようなランタイムによる実行を許可していない環境では使えなかったり、JIT によるコンパイルが必要になるため、初回実行時には時間がかかるなどの点があります。

Native AOT では、そういったデメリットを回避するため、最初からネイティブコードとしてプログラムを生成することができます。

Native DLL の作り方

今回は、.NET 8 を使用します。
もし、.NET Framework で同じくネイティブで実行できるDLL を作りたい場合は、過去に作った記事4をご参照ください。

.NET 8 でNative DLL を作るためには、.NET 8 のプロジェクトで「AOT 互換」にチェックを入れます。
Publish でプロファイルを作成し、ターゲット ランタイムで、実行環境(win-x64) を選択して、「発行(U)」をクリックすると、「ターゲットの場所」にある「native」というフォルダーに、Native AOT でビルドされたDLL が生成されます。

C# のメソッドを公開して、Native アプリから呼べるようにする

まず初めに、Native AOT の練習として、簡単なDLL を作って、C++ のプログラムから読んでみましょう。

image.png

プラットフォームターゲット : x64
トリミング可能 : 有効
AOT 互換 : 有効

プロジェクトファイルを開き、Aot の設定を追加します。
image.png

<AotAssemblies>True</AotAssemblies>
<PublishAot>True</PublishAot>

「発行(B)...」でプロファイルを作成し、プロファイルの設定で、ターゲットランタイム:win-x64 を指定します。

image.png image.png
image.png image.png
image.png image.png

こんな風に書きます。
image.png

using System.Runtime.CompilerServices;
/// <summary>
/// 引数で指定した整数を足して結果を返す
/// </summary>
/// <param name="a">引数1</param>
/// <param name="b">引数2</param>
/// <returns>足した結果</returns>
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint ="int_add")]
public static int Add(int a, int b)
{
    return a + b;
}

発行すると、native フォルダーに指定したプロファイルのネイティブDLL が生成されます。
image.png

Dependencies で見ると、きちんとUnmanagedCallersOnly のEntryPoint で指定した名前の関数が公開されていることが分かります。
image.png

C++ コンソールアプリのプロジェクトを作成して、ビルドしたDLL で公開した関数を呼んでみると、ちゃんとC# で作ったDLL がC++ からネイティブDLL の関数として呼べていることを確認できます。
image.png

UnmanagedCallersOnly の制限

UnmanagedCallersOnly を使うときには、いくつかの制限があります。

  • static にする
    まぁ、これは当然といえば当然
     
  • マネージド コードから呼び出さない
    このメソッドは外部公開専用になります。
     
  • blittable 引数のみを指定する必要があります
    blittable ではない文字列などは、IntPtr でやり取りすることになります。
    このあたりは、マーシャリングがある程度提供されていたDLL Export より手間が増えるところです。
     
  • ジェネリック型パラメーターを含めたり、ジェネリック クラスに含めたりすることはできない
    ODBC ではいろいろな型のデータを同じ引数でやり取りするところがありますが、そういうのはみんなIntPtr でのやり取りになります。

ODBC ドライバーを作るにあたって

何もないところから、フルスクラッチで書くのはハードルが高すぎるので、今回はお手本としてSQLite ODBC Driver を参考にしました。

SQLite は、ODBC ドライバーのソースが公開されていて5、実際の実装を見られるため、非常に参考になりました。

実際のODBC ドライバーに実装されている関数を見てみる

こんな風に、Microsoft のページ6にある各関数が公開されていることが分かります。
image.png

ODBC ドライバーを登録する

ODBC ドライバーをインストールすると、ODBC データソース アドミニストレーター画面 のドライバータブにインストールしたドライバーが追加され、ユーザー DSN やシステム DSN で「登録(D)...」をクリックして、データソースを追加できるようになりますが、これらを行うためのAPI があります。

これらのAPI は、ODBC コンポーネントのレジストリ エントリ にあるレジストリのキーや値を読み書きしています。

ちなみに、現在のWindows ではODBC の情報はレジストリで管理されていますが、昔のWindows 3.1 や、レジストリの無いLinux などでは、ODBC.INI というINI ファイルで管理されています。

なので、ODBC の作法としては、直接レジストリを読み書きするのではなく、提供されているAPI を使用しましょう。

レジストリの構造

  1. ドライバーの管理
    ODBC ドライバーは、HKLM\SOFTWARE\ODBC\ODBCINST.INI\ODBC Drivers で管理されています。
    image.png
    ODBCINST.INI キーにあるODBC Drivers というサブキーの中に、ドライバーの一覧が格納されています。
     
    そして、同じ階層にあるSQLite3 ODBC Driver のようなドライバー名のキーには、ドライバーのファイルパスや、そのドライバーが何個のデータソースで使われているかなどの情報が格納されています。
    image.png
     

  2. データソース
    ODBC には、ユーザーDSN とシステムDSN があります。
    それぞれのデータソース情報は、HKCU(or HKLM)\Software\ODBC\ODBC.INI に格納されています。
    image.png
    こちらもドライバーと同様に、ODBC Data Sources というサブキーにデータソースの一覧があり、データソースごとの設定値(接続先のIP アドレスや、ユーザー、パスワードなど)が、データソース名のキー内に格納されています。

ODBC データソース アドミニストレーター 画面でデータソースを登録できるようにする

ひとまず、ODBC としての実装の前段階として、C# で書いたDLL をODBC ドライバーとして認識してもらえるようにしてみます。

ドライバーの登録

SQLInstallDriverEx 関数で登録できます。
しかし、当初このことを知らず、手動でレジストリを作ったため、この部分に関してはノータッチです

データソースアドミニストレーター画面から呼ばれた時の処理

ドライバーが登録できたら、データソースアドミニストレーター画面でデータソースを追加します。
ユーザー DSN や、システム DSN タブの「追加(D)...」、「削除(R)」、「構成(C)...」がクリックされると、ドライバーのConfigDSN 関数が呼ばれます。

このとき、引数として、追加、削除、構成のどれなのかがfRequest 引数で指定されているので、それに合わせて処理を切り替えます。

構成のときは、設定ダイアログを出すことになりますが、フォーム部分をDLLに含めると、ファイルサイズが肥大化するので、今回は、Config 用のExe を用意してそれを呼ぶ形にしました。

    /// <summary>
    /// システム情報からデータ ソースを追加、変更、または削除します
    /// </summary>
    /// <param name="hwndParent">HWND: 親ウィンドウ ハンドル。 ハンドルが null の場合、関数はダイアログ ボックスを表示しません。</param>
    /// <param name="fRequest">WORD: 要求の種類。 fRequest 引数には、次のいずれかの値が含まれている必要があります。
    /// ODBC_ADD_DSN: 新しいデータ ソースを追加します。
    /// ODBC_CONFIG_DSN: 既存のデータ ソースを構成 (変更) します。
    /// ODBC_REMOVE_DSN: 既存のデータ ソースを削除します。
    /// </param>
    /// <param name="lpszDriver">LPCWSTR: 物理ドライバー名ではなく、ユーザーに提示されるドライバーの説明 (通常は関連付けられている DBMS の名前)。</param>
    /// <param name="lpszAttributes">LPCWSTR: キーワードと値のペアの形式の属性の 2 倍の null で終わるリスト。 詳細については、「コメント」を参照してください。</param>
    /// <returns>BOOL: 関数は成功した場合は TRUE を返し、失敗した場合は FALSE を返します。</returns>
    /// <example>BOOL ConfigDSN(HWND hwndParent, WORD fRequest, LPCSTR lpszDriver, LPCSTR lpszAttributes);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/configdsn-function?view=sql-server-ver16</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(ConfigDSNW))]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static bool ConfigDSNW(IntPtr hwndParent, Request fRequest, IntPtr lpszDriver, IntPtr lpszAttributes)
    {
        var driver = Marshal.PtrToStringUni(lpszDriver);
        var attributes = lpszAttributes != IntPtr.Zero ? Marshal.PtrToStringUni(lpszAttributes) : string.Empty;
        Logger.WriteLog(Logger.LogType.Debug, nameof(ConfigDSNW), $"{nameof(hwndParent)}:{hwndParent},{nameof(fRequest)}:{fRequest},{nameof(lpszDriver)}:{driver}({lpszDriver}),{nameof(lpszAttributes)}:{attributes}({lpszAttributes})");
        if (attributes != null)
        {
            foreach (var item in attributes.Split('\0'))
            {
                Logger.WriteLog(Logger.LogType.Debug, nameof(ConfigDSNW), $"attributes:{item}");
            }
        }
        if (hwndParent != IntPtr.Zero)
        {
            var exePath = @$"{Directory.GetParent(RegistryOperation.GetDriverPath(RegistryOperation.DataSourceType.System))}\CSharpODBCConfig.exe";
            Logger.WriteLog(Logger.LogType.Debug, nameof(ConfigDSNW), $"Start Process({exePath})");
            var info = new ProcessStartInfo(exePath)
            {
                ArgumentList = { hwndParent.ToString(), fRequest.ToString(), driver ?? "", attributes?.Replace('\0', '\t') ?? "" }
            };
            var ps = Process.Start(info);
            ps?.WaitForExit();
        }
        return true;
    }

しかし、ここで一つ問題があり、引数の情報では、ユーザー DSN から呼ばれたのか、システム DSN から呼ばれたのかを区別することができません。
SQLite の実装を見ると、設定の編集は削除と追加で行っているようです。

この処理を作っている時はまだ手探り状態で、SQLite の実装も調べられていなかったため、とりあえず、システム DSN 固定にしています。

CharpODBCConfig.exe Program.cs
internal static class Program
{
    /// <summary>
    ///  The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
        var productName = Application.ProductName;
        for (int i = 0; i < args.Length; i++)
        {
            Logger.WriteLog(Logger.LogType.Debug, nameof(Main), $"arg[{i}]{args[i]}");
        }
        if (args.Length < 4)
        {
            MessageBox.Show($"引数が足りません。[{args.Length}]", productName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        if (!nint.TryParse(args[0], out var handleValue))
        {
            MessageBox.Show($"ハンドルが不正です。[{args[0]}]", productName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        var handle = Control.FromHandle(handleValue);

        if (!Enum.TryParse<Defines.Request>(args[1], out var fRequest))
        {
            MessageBox.Show($"リクエストが不正です。[{args[1]}]", productName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        var DriverName = args[2];
        var Attributes = new Dictionary<string, string>();
        if (args[3].Length <= 0)
        {
            Logger.WriteLog(Logger.LogType.Debug, nameof(Main), $"AttributesInitial{DriverName}");
            Attributes.Add("DSN", DriverName);
        }
        else
        {
            foreach (var item in args[3].Split('\t'))
            {
                var pair = item.Split('=');
                Logger.WriteLog(Logger.LogType.Debug, nameof(Main), $"AttributesAdd{item}");
                Attributes.Add(pair[0], pair[1]);
            }
        }
        try
        {
            UIntPtr configMode = 0;
            if (NativeMethods.SQLGetConfigMode(ref configMode)) Logger.WriteLog(Logger.LogType.Debug, nameof(NativeMethods.SQLGetConfigMode), $"{nameof(configMode)}:{configMode}");
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
            throw;
        }
        // 今回はシステムDSN固定仕様
        NativeMethods.SQLSetConfigMode(2);

        if (fRequest == Defines.Request.ODBC_REMOVE_DSN)
        {
            NativeMethods.SQLRemoveDSNFromIni(DriverName);
            return;
        }

        // To customize application configuration such as set high DPI settings or default font,
        // see https://aka.ms/applicationconfiguration.
        ApplicationConfiguration.Initialize();
        using var formMain = new FormMain();
        formMain.DriverName = Attributes["DSN"];
        if (formMain.ShowDialog(handle) == DialogResult.OK)
        {
            switch (fRequest)
            {
                case Defines.Request.ODBC_ADD_DSN:
                    NativeMethods.SQLWriteDSNToIni(formMain.DriverName, DriverName);
                    break;
                case Defines.Request.ODBC_CONFIG_DSN:
                    break;
                default:
                    break;
            }
        }
        Application.ExitThread();
    }
}
NativeMethods.cs
public static partial class NativeMethods
{
    /// <summary>
    /// データ ソースをシステム情報に追加します
    /// </summary>
    /// <param name="lpszDSN">追加するデータ ソースの名前</param>
    /// <param name="lpszDriver">物理ドライバー名ではなく、ユーザーに提示されるドライバーの説明 (通常は関連付けられている DBMS の名前)</param>
    /// <returns>関数は成功した場合は TRUE を返し、失敗した場合は FALSE を返します</returns>
    /// <example>BOOL SQLWriteDSNToIni(LPCSTR lpszDSN, LPCSTR lpszDriver);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlwritedsntoini-function</remarks>
    [LibraryImport("odbccp32.dll", EntryPoint = "SQLWriteDSNToIniW", StringMarshalling = StringMarshalling.Utf16)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static partial bool SQLWriteDSNToIni(string lpszDSN, string lpszDriver);

    /// <summary>
    /// システム情報からデータ ソースを削除します
    /// </summary>
    /// <param name="lpszDSN">[入力] 削除するデータ ソースの名前</param>
    /// <returns>データ ソースが削除された場合、またはデータ ソースがOdbc.ini ファイルにない場合に TRUE を返します。 データ ソースの削除に失敗した場合は FALSE を返します</returns>
    /// <example>BOOL SQLRemoveDSNFromIni(LPCSTR lpszDSN);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlremovedsnfromini-function</remarks>
    [LibraryImport("odbccp32.dll", EntryPoint = "SQLRemoveDSNFromIniW", StringMarshalling = StringMarshalling.Utf16)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static partial bool SQLRemoveDSNFromIni(string lpszDSN);

    /// <summary>
    /// DSN 値を一覧表示するOdbc.iniエントリがシステム情報内のどこにあるかを示す構成モードを取得します
    /// </summary>
    /// <param name="pwConfigMode"></param>
    /// <returns>関数は成功した場合は TRUE を返し、失敗した場合は FALSE を返します</returns>
    /// <example>BOOL SQLGetConfigMode(UWORD* pwConfigMode);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlgetconfigmode-function</remarks>
    [LibraryImport("odbccp32.dll", EntryPoint = "SQLGetConfigMode")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static partial bool SQLGetConfigMode(ref UIntPtr pwConfigMode);

    /// <summary>
    /// DSN 値をリストするOdbc.ini エントリがシステム情報内のどこにあるかを示す構成モードを設定します
    /// </summary>
    /// <param name="pwConfigMode">[入力] インストーラー構成モード</param>
    /// <returns>関数は成功した場合は TRUE を返し、失敗した場合は FALSE を返します</returns>
    /// <example>BOOL SQLSetConfigMode(UWORD wConfigMode);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlsetconfigmode-function</remarks>
    [LibraryImport("odbccp32.dll", EntryPoint = "SQLSetConfigMode")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static partial bool SQLSetConfigMode(ushort pwConfigMode);
}

ログ出力機構を作る(重要)

今回のようなケースでは、ログ出力の仕組みはとても重要です。
一応、↓のODBC のトレース ログでも、最低限の情報はわかりますが、配列やポインタの先に書かれたデータまでは分からない部分もあるためで、それだけでは不十分です。

とは言っても、あまり複雑な仕組みを作る必要はなく、単にテキストファイルに時刻や関数名、変数のデータを出力するだけの仕組みがあれば十分です。

Logger.cs
public static class Logger
{
    private const string LogfilePath = @"C:\ODBC\CSharpODBC.log";
    public enum LogType
    {
        Information,
        Debug,
        Error
    }
    private static readonly StreamWriter _writer = new(LogfilePath, true, Encoding.UTF8);

    public static void WriteLog(LogType logType, string Title, string message)
    {
        _writer.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss.fff} [{logType}] [{Title}] {message}");
        _writer.Flush();
    }
}

初めに、お手本を見てみる

今回作るのは、あくまでHello, World! レベルなので、最初から一通りの機能を実装しようとするのはおすすめできません。
 
まず、どのような関数を実装しなければならないのか、また、どんな順番で呼ばれ、引数にどんな値がセットされているのかを知ることが重要です。
 
そこで、お手本としてSQLite ODBC Driver でSQL を実行した時のログを取得してみましょう。
幸い、ODBC では、簡単にログを取得できる機能が提供されています。
 
ODBC データソース アドミニストレーター画面の「トレース」タブを開き、「ログ ファイルのパス(L)」にログファイルの保存先ファイルパスを指定し、「トレース開始(T)」をクリックします。
 
ボタンが「トレース停止(T)」という表示になれば、ログファイルが出力されている状態になっています。
この状態で、A5:SQL Mk-2 からSQL を実行してログファイルを取得してみます。
image.png

どんな風に動いているかを見てみる

SQLite に対してSQL を実行した時のログファイルはこんな感じになっています。

SQL(SQLite3).log
A5M2            a54-8d90	ENTER SQLAllocEnv 
		HENV *              0x00007FFC14FFB510

A5M2            a54-8d90	EXIT  SQLAllocEnv  with return code 0 (SQL_SUCCESS)
		HENV *              0x00007FFC14FFB510 ( 0x0000019CFBE47E80)

A5M2            a54-8d90	ENTER SQLGetEnvAttr 
		SQLHENV             0x0000019CFBE47E80
		SQLINTEGER                 201 <SQL_ATTR_CONNECTION_POOLING>
		SQLPOINTER          0x0000006EFFEFBFE0
		SQLINTEGER                   4 
		SQLINTEGER *        0x0000006EFFEFBFE8

A5M2            a54-8d90	EXIT  SQLGetEnvAttr  with return code 0 (SQL_SUCCESS)
		SQLHENV             0x0000019CFBE47E80
		SQLINTEGER                 201 <SQL_ATTR_CONNECTION_POOLING>
		SQLPOINTER          0x0000006EFFEFBFE0 (0) <SQL_CP_OFF>
		SQLINTEGER                   4 
		SQLINTEGER *        0x0000006EFFEFBFE8 (4096)

A5M2            a54-8d90	ENTER SQLAllocEnv 
		HENV *              0x00007FFC14FFB518

A5M2            a54-8d90	EXIT  SQLAllocEnv  with return code 0 (SQL_SUCCESS)
		HENV *              0x00007FFC14FFB518 ( 0x0000019CFBE47F60)

A5M2            a54-8d90	ENTER SQLSetEnvAttr 
		SQLHENV             0x0000019CFBE47F60
		SQLINTEGER                 201 <SQL_ATTR_CONNECTION_POOLING>
		SQLPOINTER                 0 <SQL_CP_OFF>
		SQLINTEGER                  -6 

A5M2            a54-8d90	EXIT  SQLSetEnvAttr  with return code 0 (SQL_SUCCESS)
		SQLHENV             0x0000019CFBE47F60
		SQLINTEGER                 201 <SQL_ATTR_CONNECTION_POOLING>
		SQLPOINTER                 0 <SQL_CP_OFF>
		SQLINTEGER                  -6 

A5M2            a54-8d90	ENTER SQLAllocConnect 
		HENV                0x0000019CFBE47F60
		HDBC *              0x0000006EFFEFCBA0

A5M2            a54-8d90	EXIT  SQLAllocConnect  with return code 0 (SQL_SUCCESS)
		HENV                0x0000019CFBE47F60
		HDBC *              0x0000006EFFEFCBA0 ( 0x0000019CFBE3EBF0)

まず、ログファイルの構成としては、ENTER とEXIT というのが1つのペアになっていて、ENTER はアプリ(A5:SQL Mk-2)からの呼び出し、とEXIT はODBC Driverからの戻り値です。
そして、関数名と共に、引数の型や数、並び順といったシグネチャーとその中の値が並んでいます。
ご丁寧に定数名まで出してくれるのはとても助かりますね。

ただし、接続してSQL を実行しただけで、ログファイルが2,000 行ほど出力されるので、この時点で少し気が萎えそうになります。

ひとまず、呼ばれている関数を並べてみると、全部で26個でした。
ちなみに、関数名の末尾にWが付いているものはUnicode 関数であることを表します。
C# の内部文字コードはUTF-16 なので、Unicode なら変換なしで扱えて都合がいいです。

Methods
SQLAllocEnv 
SQLGetEnvAttr 
SQLSetEnvAttr 
SQLAllocConnect 
SQLGetInfoW 
SQLSetConnectAttrW 
SQLDriverConnectW 
SQLGetFunctions 
SQLGetConnectAttrW 
SQLAllocStmt 
SQLSetStmtAttrW 
SQLGetStmtAttrW 
SQLSetDescFieldW 
SQLSetDescField 
SQLFreeStmt 
SQLPrepareW 
SQLNumParams 
SQLExecDirectW 
SQLParamOptions 
SQLRowCount 
SQLNumResultCols 
SQLDescribeColW 
SQLColAttributesW 
SQLNativeSqlW 
SQLBindCol 
SQLExtendedFetch 

ログから見た処理の流れ

ログファイルから処理の流れを抜粋するとこんな風になっています。

SQLAllocEnv                                            <-接続処理
SQLGetEnvAttr      201 <SQL_ATTR_CONNECTION_POOLING>
SQLAllocEnv 
SQLSetEnvAttr      201 <SQL_ATTR_CONNECTION_POOLING>
SQLAllocConnect 
SQLGetInfoW            <SQL_ODBC_VER>
SQLSetConnectAttrW 103 <SQL_ATTR_LOGIN_TIMEOUT>
SQLDriverConnectW 
SQLGetInfoW          6 <SQL_DRIVER_NAME>              <-ドライバー情報の取得
SQLGetInfoW         77 <SQL_DRIVER_ODBC_VER>
SQLGetInfoW         79 <SQL_POS_OPERATIONS>
SQLGetInfoW         83 <SQL_STATIC_SENSITIVITY>
SQLGetInfoW         78 <SQL_LOCK_TYPES>
SQLGetInfoW         81 <SQL_GETDATA_EXTENSIONS>
SQLGetInfoW         72 <SQL_TXN_ISOLATION_OPTION>
SQLGetInfoW         82 <SQL_BOOKMARK_PERSISTENCE>
SQLGetInfoW         44 <SQL_SCROLL_OPTIONS>
SQLGetInfoW         43 <SQL_SCROLL_CONCURRENCY>
SQLGetInfoW        144 <SQL_DYNAMIC_CURSOR_ATTRIBUTES1>
SQLGetInfoW        150 <SQL_KEYSET_CURSOR_ATTRIBUTES1>
SQLGetInfoW        167 <SQL_STATIC_CURSOR_ATTRIBUTES1>
SQLGetInfoW        146 <SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1>
SQLGetInfoW        151 <SQL_KEYSET_CURSOR_ATTRIBUTES2>
SQLGetInfoW        168 <SQL_STATIC_CURSOR_ATTRIBUTES2>
SQLGetInfoW        111 <SQL_NEED_LONG_DATA_LEN>
SQLGetInfoW         16 <SQL_DATABASE_NAME>
SQLGetInfoW         23 <SQL_CURSOR_COMMIT_BEHAVIOR>
SQLGetInfoW         24 <SQL_CURSOR_ROLLBACK_BEHAVIOR>
SQLGetInfoW         46 <SQL_TXN_CAPABLE>
SQLSetConnectAttrW   1 <SQL_ATTR_MAX_ROWS>
SQLSetConnectAttrW   0 <SQL_ATTR_QUERY_TIMEOUT>
SQLGetInfoW         17 <SQL_DBMS_NAME>
SQLGetInfoW          6 <SQL_DRIVER_NAME>
SQLGetFunctions  999                               <-関数一覧取得
SQLGetInfoW          1 <SQL_ACTIVE_STATEMENTS>
SQLGetConnectAttrW 109 <SQL_ATTR_CURRENT_CATALOG>514 
SQLGetInfoW         26 <SQL_DEFAULT_TXN_ISOLATION>
SQLGetInfoW         17 <SQL_DBMS_NAME>
SQLGetInfoW         18 <SQL_DBMS_VER>
SQLAllocStmt                                      <-ステートメントハンドル取得
SQLSetStmtAttrW     18 <SQL_ATTR_PARAM_BIND_TYPE>
SQLGetStmtAttrW  10013 <SQL_ATTR_IMP_PARAM_DESC>  0 
SQLGetStmtAttrW  10011 <SQL_ATTR_APP_PARAM_DESC>  0 
SQLSetDescFieldW 
SQLSetDescField 
SQLSetStmtAttrW      0 <SQL_ATTR_QUERY_TIMEOUT>
SQLFreeStmt          3 <SQL_RESET_PARAMS>
SQLPrepareW         "SELECT 'Hello, World!' AS TEST\ d\ a"  <- SQL の実行開始
SQLNumParams 
SQLGetInfoW         36 <SQL_MULT_RESULT_SETS>
SQLGetStmtAttrW      6 <SQL_ATTR_CURSOR_TYPE>
SQLGetStmtAttrW      7 <SQL_ATTR_CONCURRENCY>
SQLGetConnectAttrW 108 <SQL_ATTR_TXN_ISOLATION>
SQLSetStmtAttrW      1 <SQL_ATTR_MAX_ROWS>  10000
SQLExecDirectW       "SELECT INVALID SELECT STATEMENT TO FORCE ODBC DRIVER TO UNPREPARED STATE\ 0"
SQLFreeStmt          0 <SQL_CLOSE>
SQLSetStmtAttrW      7 <SQL_ATTR_CONCURRENCY>  1 <SQL_CONCUR_READ_ONLY>
SQLGetStmtAttrW      7 <SQL_ATTR_CONCURRENCY>
SQLSetStmtAttrW      6 <SQL_ATTR_CURSOR_TYPE>  0 <SQL_CURSOR_FORWARD_ONLY>
SQLGetStmtAttrW      6 <SQL_ATTR_CURSOR_TYPE> -5 
SQLParamOptions 
SQLExecDirectW       "SELECT 'Hello, World!' AS TEST\ d\ a"   <-実行するSQL をセット
SQLGetDiagFieldW 
SQLRowCount 
SQLNumResultCols 
SQLNumResultCols 
SQLDescribeColW
SQLColAttributesW   18 <SQL_DESC_LABEL>
SQLColAttributesW   10 <SQL_DESC_UPDATABLE>
SQLGetStmtAttrW      6 <SQL_ATTR_CURSOR_TYPE> -5 
SQLGetInfoW         30 <SQL_MAX_COLUMN_NAME_LEN> 
SQLGetInfoW         34 <SQL_MAX_CATALOG_NAME_LEN>
SQLGetInfoW         32 <SQL_MAX_SCHEMA_NAME_LEN>
SQLGetInfoW         35 <SQL_MAX_TABLE_NAME_LEN>
SQLColAttributesW   15 <SQL_DESC_TABLE_NAME>
SQLColAttributesW   17 <SQL_DESC_CATALOG_NAME>
SQLColAttributesW   16 <SQL_DESC_SCHEMA_NAME>
SQLGetDiagRecW 
SQLColAttributesW   11 <SQL_DESC_AUTO_UNIQUE_VALUE>
SQLGetInfoW         17 <SQL_DBMS_NAME>
SQLGetInfoW         36 <SQL_MULT_RESULT_SETS>
SQLGetInfoW         39 <SQL_OWNER_TERM>
SQLGetInfoW         18 <SQL_DBMS_VER>
SQLGetInfoW         29 <SQL_IDENTIFIER_QUOTE_CHAR>
SQLGetInfoW         41 <SQL_CATALOG_NAME_SEPARATOR>
SQLNativeSqlW        "SELECT 'Hello, World!' AS TEST\ d\ a\ 0"
SQLGetStmtAttrW      6 <SQL_ATTR_CURSOR_TYPE> -5 
SQLSetStmtAttrW      9 <SQL_ROWSET_SIZE>
SQLGetStmtAttrW      9 <SQL_ROWSET_SIZE> -5 
SQLSetStmtAttrW      5 <SQL_ATTR_ROW_BIND_TYPE>
SQLSetStmtAttrW     11 <SQL_ATTR_RETRIEVE_DATA>
SQLSetStmtAttrW     11 <SQL_ATTR_RETRIEVE_DATA>
SQLBindCol          -8 <SQL_C_WCHAR>                               <-SQL の結果を返してもらう変数のポインターをセット
SQLSetStmtAttrW      9 <SQL_ROWSET_SIZE>
SQLExtendedFetch     1 <SQL_FETCH_NEXT>                            <-結果の受け取り(1行目)
SQLExtendedFetch     1 <SQL_FETCH_NEXT>                            <-結果の受け取り(2行目)ここでSQL_NO_DATA が返るので最後まで取得したことが分かる
SQLFreeStmt          2 <SQL_UNBIND>
SQLFreeStmt          0 <SQL_CLOSE>

リファレンス資料はどこ?

さて、今回のようにC# でWindows API を触ろうとしたときに毎回地味に苦労するのが、シグネチャーを調べて、C# の書式に変換する作業です。
特に、ODBC の場合、型が全て独自に定義された名前になっており、そのままでは、その引数が何バイトの型なのかさっぱり分かりません。

これを解決する方法はいくつかありますが、今回は素直にC++ で書いて調べることにします。
どのみち、SQLite の関数を実際に叩いて動作を比較したりするのにもC++ を使うので、あまり手間は増えませんし、Visual Studio なら別名で定義されている型でも、マウスカーソルを当てるだけで簡単に元の型やサイズを知ることができて便利です。

型を調べる

そんなわけで、使われている型の一覧をまとめたのがこちらの表になります。
wchar_t は、blittable ではないので、IntPtr でやり取りすることになるため、だいたいIntPtr で、あとはshort, ushort, int, long, ulong なので、それほど種類はありません。

SQL C++(バイト数) C#
HDBC void*(8) IntPtr
HENV void*(8) IntPtr
HSTMT void*(8) IntPtr
HWND void*(8) IntPtr
PTR void*(8) IntPtr
SQLHANDLE void*(8) IntPtr
SQLHDBC void*(8) IntPtr
SQLHDESC void*(8) IntPtr
SQLHENV void*(8) IntPtr
SQLHSTMT void*(8) IntPtr
SQLPOINTER void*(8) IntPtr
SQLWCHAR wchar_t(2) IntPtr
WCHAR wchar_t(2) IntPtr
SQLLEN long long(8) long
SQLULEN unsigned long long(8) ulong
SDWORD long(4) int
SQLINTEGER long(4) int
SQLSMALLINT short(2) short
SQLUSMALLINT ushort(2) ushort
SWORD short(2) short
UWORD unsigned short(2) ushort

定数を調べる

いろいろな定数がありますが、C# ではEnum があるので、引数で定数を扱う箇所は、Enum でまとめておくと便利です。

定数定義
Define.cs
public static class Defines
{
    public enum Request
    {
        /// <summary>
        /// 新しいデータ ソースを追加します。
        /// </summary>
        ODBC_ADD_DSN = 1,
        /// <summary>
        /// 既存のデータ ソースを構成 (変更) します。
        /// </summary>
        ODBC_CONFIG_DSN = 2,
        /// <summary>
        /// 既存のデータ ソースを削除します。
        /// </summary>
        ODBC_REMOVE_DSN = 3
    }

    public enum SQLRETURN
    {
        SQL_SUCCESS           = 0,
        SQL_SUCCESS_WITH_INFO = 1,
        SQL_ERROR             = -1,
        SQL_INVALID_HANDLE    = -2,
        SQL_STILL_EXECUTING   = 2,
        SQL_NO_DATA           = 100,
    }

    public enum SQLTypeEnum : short
    {
        SQL_UNKNOWN_TYPE    = 0,
        SQL_CHAR            = 1,
        SQL_NUMERIC         = 2,
        SQL_DECIMAL         = 3,
        SQL_INTEGER         = 4,
        SQL_SMALLINT        = 5,
        SQL_FLOAT           = 6,
        SQL_REAL            = 7,
        SQL_DOUBLE          = 8,
        SQL_DATETIME        = 9,
        SQL_VARCHAR         = 12,
        SQL_TYPE_DATE       = 91,
        SQL_TYPE_TIME       = 92,
        SQL_TYPE_TIMESTAMP  = 93,
        SQL_WCHAR           = -8,
        SQL_WVARCHAR        = -9,
        SQL_WLONGVARCHAR    = -10
    }

    public enum AttributeEnum : long
    {
        SQL_ATTR_QUERY_TIMEOUT              = 0,
        SQL_ATTR_MAX_ROWS                   = 1,
        SQL_NOSCAN                          = 2,
        SQL_MAX_LENGTH                      = 3,
        SQL_ASYNC_ENABLE                    = 4,
        SQL_BIND_TYPE                       = 5,
        SQL_CURSOR_TYPE                     = 6,
        SQL_CONCURRENCY                     = 7,
        SQL_KEYSET_SIZE                     = 8,
        SQL_ROWSET_SIZE                     = 9,
        SQL_ATTR_ACCESS_MODE                = 101,
        SQL_ATTR_AUTOCOMMIT                 = 102,
        SQL_ATTR_LOGIN_TIMEOUT              = 103,
        SQL_ATTR_TRACE                      = 104,
        SQL_ATTR_TRACEFILE                  = 105,
        SQL_ATTR_TRANSLATE_LIB              = 106,
        SQL_ATTR_TRANSLATE_OPTION           = 107,
        SQL_ATTR_TXN_ISOLATION              = 108,
        SQL_ATTR_CURRENT_CATALOG            = 109,
        SQL_ATTR_ODBC_CURSORS               = 110,
        SQL_ATTR_QUIET_MODE                 = 111,
        SQL_ATTR_PACKET_SIZE                = 112,
        SQL_ATTR_CONNECTION_TIMEOUT         = 113,
        SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE = 117,
        SQL_ATTR_ODBC_VERSION               = 200,
        SQL_ATTR_CONNECTION_POOLING         = 201,
        SQL_ATTR_CP_MATCH                   = 202,
        SQL_ATTR_ENLIST_IN_DTC              = 1207,
        SQL_ATTR_CONNECTION_DEAD            = 1209,
        SQL_ATTR_OUTPUT_NTS                 = 10001,
    }

    public enum InfoTypeEnum : ushort
    {
        SQL_ACTIVE_CONNECTIONS              = 0, /* MAX_DRIVER_CONNECTIONS */
        SQL_ACTIVE_STATEMENTS               = 1, /* MAX_CONCURRENT_ACTIVITIES */
        SQL_DRIVER_HDBC                     = 3,
        SQL_DRIVER_HENV                     = 4,
        SQL_DRIVER_HSTMT                    = 5,
        SQL_DRIVER_NAME                     = 6,
        SQL_DRIVER_VER                      = 7,
        SQL_FETCH_DIRECTION                 = 8,
        SQL_ODBC_API_CONFORMANCE            = 9,
        SQL_ODBC_VER                        = 10,
        SQL_ROW_UPDATES                     = 11,
        SQL_ODBC_SAG_CLI_CONFORMANCE        = 12,
        SQL_SEARCH_PATTERN_ESCAPE           = 14,
        SQL_ODBC_SQL_CONFORMANCE            = 15,
        SQL_DATABASE_NAME                   = 16,
        SQL_DBMS_NAME                       = 17,
        SQL_DBMS_VER                        = 18,
        SQL_ACCESSIBLE_TABLES               = 19,
        SQL_ACCESSIBLE_PROCEDURES           = 20,
        SQL_PROCEDURES                      = 21,
        SQL_CONCAT_NULL_BEHAVIOR            = 22,
        SQL_CURSOR_COMMIT_BEHAVIOR          = 23,
        SQL_CURSOR_ROLLBACK_BEHAVIOR        = 24,
        SQL_DEFAULT_TXN_ISOLATION           = 26,
        SQL_EXPRESSIONS_IN_ORDERBY          = 27,
        SQL_IDENTIFIER_CASE                 = 28,
        SQL_IDENTIFIER_QUOTE_CHAR           = 29,
        SQL_MAX_COLUMN_NAME_LEN             = 30,
        SQL_MAX_CURSOR_NAME_LEN             = 31,
        SQL_MAX_OWNER_NAME_LEN              = 32, /* MAX_SCHEMA_NAME_LEN */
        SQL_MAX_PROCEDURE_NAME_LEN          = 33,
        SQL_MAX_QUALIFIER_NAME_LEN          = 34, /* MAX_CATALOG_NAME_LEN */
        SQL_MAX_TABLE_NAME_LEN              = 35,
        SQL_MULT_RESULT_SETS                = 36,
        SQL_MULTIPLE_ACTIVE_TXN             = 37,
        SQL_OUTER_JOINS                     = 38,
        SQL_OWNER_TERM                      = 39,
        SQL_PROCEDURE_TERM                  = 40,
        SQL_QUALIFIER_NAME_SEPARATOR        = 41,
        SQL_QUALIFIER_TERM                  = 42,
        SQL_SCROLL_CONCURRENCY              = 43,
        SQL_SCROLL_OPTIONS                  = 44,
        SQL_TABLE_TERM                      = 45,
        SQL_TXN_CAPABLE                     = 46,
        SQL_USER_NAME                       = 47,
        SQL_CONVERT_FUNCTIONS               = 48,
        SQL_NUMERIC_FUNCTIONS               = 49,
        SQL_STRING_FUNCTIONS                = 50,
        SQL_SYSTEM_FUNCTIONS                = 51,
        SQL_TIMEDATE_FUNCTIONS              = 52,
        SQL_CONVERT_BIGINT                  = 53,
        SQL_CONVERT_BINARY                  = 54,
        SQL_CONVERT_BIT                     = 55,
        SQL_CONVERT_CHAR                    = 56,
        SQL_CONVERT_DATE                    = 57,
        SQL_CONVERT_DECIMAL                 = 58,
        SQL_CONVERT_DOUBLE                  = 59,
        SQL_CONVERT_FLOAT                   = 60,
        SQL_CONVERT_INTEGER                 = 61,
        SQL_CONVERT_LONGVARCHAR             = 62,
        SQL_CONVERT_NUMERIC                 = 63,
        SQL_CONVERT_REAL                    = 64,
        SQL_CONVERT_SMALLINT                = 65,
        SQL_CONVERT_TIME                    = 66,
        SQL_CONVERT_TIMESTAMP               = 67,
        SQL_CONVERT_TINYINT                 = 68,
        SQL_CONVERT_VARBINARY               = 69,
        SQL_CONVERT_VARCHAR                 = 70,
        SQL_CONVERT_LONGVARBINARY           = 71,
        SQL_TXN_ISOLATION_OPTION            = 72,
        SQL_ODBC_SQL_OPT_IEF                = 73,     /* SQL_INTEGRITY */
        SQL_CORRELATION_NAME                = 74,
        SQL_NON_NULLABLE_COLUMNS            = 75,
        SQL_DRIVER_HLIB                     = 76,
        SQL_DRIVER_ODBC_VER                 = 77,
        SQL_LOCK_TYPES                      = 78,
        SQL_POS_OPERATIONS                  = 79,
        SQL_POSITIONED_STATEMENTS           = 80,
        SQL_GETDATA_EXTENSIONS              = 81,
        SQL_BOOKMARK_PERSISTENCE            = 82,
        SQL_STATIC_SENSITIVITY              = 83,
        SQL_FILE_USAGE                      = 84,
        SQL_COLUMN_ALIAS                    = 87,
        SQL_GROUP_BY                        = 88,
        SQL_KEYWORDS                        = 89,
        SQL_ORDER_BY_COLUMNS_IN_SELECT      = 90,
        SQL_OWNER_USAGE                     = 91,
        SQL_QUALIFIER_USAGE                 = 92,
        SQL_QUOTED_IDENTIFIER_CASE          = 93,
        SQL_SUBQUERIES                      = 95,
        SQL_UNION                           = 96,
        SQL_MAX_ROW_SIZE_INCLUDES_LONG      = 103,
        SQL_MAX_CHAR_LITERAL_LEN            = 108,
        SQL_TIMEDATE_ADD_INTERVALS          = 109,
        SQL_TIMEDATE_DIFF_INTERVALS         = 110,
        SQL_NEED_LONG_DATA_LEN              = 111,
        SQL_MAX_BINARY_LITERAL_LEN          = 112,
        SQL_LIKE_ESCAPE_CLAUSE              = 113,
        SQL_QUALIFIER_LOCATION              = 114,
        SQL_DYNAMIC_CURSOR_ATTRIBUTES1      = 144,
        SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1 = 146,
        SQL_KEYSET_CURSOR_ATTRIBUTES1       = 150,
        SQL_KEYSET_CURSOR_ATTRIBUTES2       = 151,
        SQL_STATIC_CURSOR_ATTRIBUTES1       = 167,
        SQL_STATIC_CURSOR_ATTRIBUTES2       = 168,
    }

    public enum SQL_CBEnum : short
    {
        SQL_CB_DELETE                       = 0,
        SQL_CB_CLOSE                        = 1,
        SQL_CB_PRESERVE                     = 2
    }

    public enum AttrODBCVersion
    {
        SQL_OV_ODBC2                        = 2,
        SQL_OV_ODBC3                        = 3,
        SQL_OV_ODBC3_80                     = 380,
    }

    public enum AttrConnectionPoolingEnum
    {
        SQL_CP_OFF                          = 0,
        SQL_CP_ONE_PER_DRIVER               = 1,
        SQL_CP_ONE_PER_HENV                 = 2,
        SQL_CP_DRIVER_AWARE                 = 3,
    }

    public enum AttrConnectionPoolingMatchEnum
    {
        SQL_CP_STRICT_MATCH                 = 0,
        SQL_CP_RELAXED_MATCH                = 1,
    }

    public enum SQL_POS_POSITIONEnum
    {
        SQL_POS_POSITION = 1,
        SQL_POS_REFRESH  = 2,
        SQL_POS_UPDATE   = 4,
        SQL_POS_DELETE   = 8,
        SQL_POS_ADD      =16,
    }

    public enum SQL_FD_FETCHEnum
    {
        SQL_FD_FETCH_NEXT     = 1,
        SQL_FD_FETCH_FIRST    = 2,
        SQL_FD_FETCH_LAST     = 4,
        SQL_FD_FETCH_PRIOR    = 8,
        SQL_FD_FETCH_ABSOLUTE = 16
    }

    public enum SQL_LOCKEnum
    {
        SQL_LCK_NO_CHANGE = 1,
        SQL_LCK_EXCLUSIVE = 2,
        SQL_LCK_UNLOCK    = 4
    }

    public enum SQL_GDEnum
    {
        SQL_GD_ANY_COLUMN = 1,
        SQL_GD_ANY_ORDER  = 2,
        SQL_GD_BLOCK      = 4,
        SQL_GD_BOUND      = 8,
    }

    public enum SQL_TXN
    {
        SQL_TXN_READ_UNCOMMITTED = 1,
        SQL_TXN_READ_COMMITTED   = 2,
        SQL_TXN_REPEATABLE_READ  = 4,
        SQL_TXN_SERIALIZABLE     = 8
    }

    public enum SQL_SCCOEnum
    {
        SQL_SCCO_READ_ONLY  = 1,
        SQL_SCCO_LOCK       = 2,
        SQL_SCCO_OPT_ROWVER = 4,
        SQL_SCCO_OPT_VALUES = 8
    }

    public enum SQL_SOEnum
    {
        SQL_SO_FORWARD_ONLY  = 1,
        SQL_SO_KEYSET_DRIVEN = 2,
        SQL_SO_DYNAMIC       = 4,
        SQL_SO_MIXED         = 8,
        SQL_SO_STATIC        = 16
    }

    public enum SQL_TCEnum
    {
        SQL_TC_NONE       = 0,
        SQL_TC_DML        = 1,
        SQL_TC_ALL        = 2,
        SQL_TC_DDL_COMMIT = 3,
        SQL_TC_DDL_IGNORE = 4,
    }

    public enum SQL_BPEnum
    {
        SQL_BP_CLOSE       = 1,
        SQL_BP_DELETE      = 2,
        SQL_BP_DROP        = 4,
        SQL_BP_TRANSACTION = 8,
        SQL_BP_UPDATE      = 16,
        SQL_BP_OTHER_HSTMT = 32,
        SQL_BP_SCROLL      = 64
    }

    public enum SQL_ICEnum
    {
        SQL_IC_UPPER     = 1,
        SQL_IC_LOWER     = 2,
        SQL_IC_SENSITIVE = 3,
        SQL_IC_MIXED     = 4
    }

    public enum StatmentAttributesEnum
    {
        SQL_ATTR_QUERY_TIMEOUT              = 0,
        SQL_ATTR_MAX_ROWS                   = 1,
        SQL_ATTR_ROW_BIND_TYPE              = 5,
        SQL_ATTR_CURSOR_TYPE                = 6,
        SQL_ATTR_CONCURRENCY                = 7,
        SQL_ROWSET_SIZE                     = 9,
        SQL_ATTR_SIMULATE_CURSOR            = 10,
        SQL_ATTR_RETRIEVE_DATA              = 11,
        SQL_ATTR_USE_BOOKMARKS              = 12,
        SQL_ATTR_ROW_NUMBER                 = 14,
        SQL_ATTR_PARAM_BIND_OFFSET_PTR      = 17,
        SQL_ATTR_PARAM_BIND_TYPE            = 18,
        SQL_ATTR_PARAM_OPERATION_PTR        = 19,
        SQL_ATTR_PARAM_STATUS_PTR           = 20,
        SQL_ATTR_PARAMS_PROCESSED_PTR       = 21,
        SQL_ATTR_PARAMSET_SIZE              = 22,
        SQL_ATTR_ROW_BIND_OFFSET_PTR        = 23,
        SQL_ATTR_ROW_OPERATION_PTR          = 24,
        SQL_ATTR_ROW_STATUS_PTR             = 25,
        SQL_ATTR_ROWS_FETCHED_PTR           = 26,
        SQL_ATTR_ROW_ARRAY_SIZE             = 27,
        SQL_ATTR_APP_ROW_DESC               = 10010,
        SQL_ATTR_APP_PARAM_DESC             = 10011,
        SQL_ATTR_IMP_ROW_DESC               = 10012,
        SQL_ATTR_IMP_PARAM_DESC             = 10013,
    }

    public enum FunctionsEnum : ushort
    {
        SQL_API_ALL_FUNCTIONS       = 0,
        SQL_API_SQLALLOCCONNECT     = 1,
        SQL_API_SQLALLOCENV         = 2,
        SQL_API_SQLALLOCSTMT        = 3,
        SQL_API_SQLBINDCOL          = 4,
        SQL_API_SQLCANCEL           = 5,
        SQL_API_SQLCOLATTRIBUTE     = 6,
        SQL_API_SQLCONNECT          = 7,
        SQL_API_SQLDESCRIBECOL      = 8,
        SQL_API_SQLDISCONNECT       = 9,
        SQL_API_SQLERROR            = 10,
        SQL_API_SQLEXECDIRECT       = 11,
        SQL_API_SQLEXECUTE          = 12,
        SQL_API_SQLFETCH            = 13,
        SQL_API_SQLFREECONNECT      = 14,
        SQL_API_SQLFREEENV          = 15,
        SQL_API_SQLFREESTMT         = 16,
        SQL_API_SQLGETCURSORNAME    = 17,
        SQL_API_SQLNUMRESULTCOLS    = 18,
        SQL_API_SQLPREPARE          = 19,
        SQL_API_SQLROWCOUNT         = 20,
        SQL_API_SQLSETCURSORNAME    = 21,
        SQL_API_SQLSETPARAM         = 22,
        SQL_API_SQLTRANSACT         = 23,
        SQL_API_SQLBULKOPERATIONS   = 24,
        SQL_API_SQLCOLUMNS          = 40,
        SQL_API_SQLDRIVERCONNECT    = 41,
        SQL_API_SQLGETCONNECTOPTION = 42,
        SQL_API_SQLGETDATA          = 43,
        SQL_API_SQLGETFUNCTIONS     = 44,
        SQL_API_SQLGETINFO          = 45,
        SQL_API_SQLGETSTMTOPTION    = 46,
        SQL_API_SQLGETTYPEINFO      = 47,
        SQL_API_SQLPARAMDATA        = 48,
        SQL_API_SQLPUTDATA          = 49,
        SQL_API_SQLSETCONNECTOPTION = 50,
        SQL_API_SQLSETSTMTOPTION    = 51,
        SQL_API_SQLSPECIALCOLUMNS   = 52,
        SQL_API_SQLSTATISTICS       = 53,
        SQL_API_SQLTABLES           = 54,
        SQL_API_SQLBROWSECONNECT    = 55,
        SQL_API_SQLCOLUMNPRIVILEGES = 56,
        SQL_API_SQLDATASOURCES      = 57,
        SQL_API_SQLDESCRIBEPARAM    = 58,
        SQL_API_SQLEXTENDEDFETCH    = 59,
        SQL_API_SQLFOREIGNKEYS      = 60,
        SQL_API_SQLMORERESULTS      = 61,
        SQL_API_SQLNATIVESQL        = 62,
        SQL_API_SQLNUMPARAMS        = 63,
        SQL_API_SQLPARAMOPTIONS     = 64,
        SQL_API_SQLPRIMARYKEYS      = 65,
        SQL_API_SQLPROCEDURECOLUMNS = 66,
        SQL_API_SQLPROCEDURES       = 67,
        SQL_API_SQLSETPOS           = 68,
        SQL_API_SQLSETSCROLLOPTIONS = 69,
        SQL_API_SQLTABLEPRIVILEGES  = 70,
        SQL_API_SQLDRIVERS          = 71,
        SQL_API_SQLBINDPARAMETER    = 72,
        SQL_API_ODBC3_ALL_FUNCTIONS = 999,
        SQL_API_SQLALLOCHANDLE      = 1001,
        SQL_API_SQLBINDPARAM        = 1002,
        SQL_API_SQLCLOSECURSOR      = 1003,
        SQL_API_SQLCOPYDESC         = 1004,
        SQL_API_SQLENDTRAN          = 1005,
        SQL_API_SQLFREEHANDLE       = 1006,
        SQL_API_SQLGETCONNECTATTR   = 1007,
        SQL_API_SQLGETDESCFIELD     = 1008,
        SQL_API_SQLGETDESCREC       = 1009,
        SQL_API_SQLGETDIAGFIELD     = 1010,
        SQL_API_SQLGETDIAGREC       = 1011,
        SQL_API_SQLGETENVATTR       = 1012,
        SQL_API_SQLGETSTMTATTR      = 1014,
        SQL_API_SQLSETCONNECTATTR   = 1016,
        SQL_API_SQLSETDESCFIELD     = 1017,
        SQL_API_SQLSETDESCREC       = 1018,
        SQL_API_SQLSETENVATTR       = 1019,
        SQL_API_SQLSETSTMTATTR      = 1020,
        SQL_API_SQLFETCHSCROLL      = 1021,
        SQL_API_SQLCANCELHANDLE     = 1550,
        SQL_API_SQLCOMPLETEASYNC    = 1551,
    }

    public enum SQL_CURSOR_COMMIT_BEHAVIOR_Enum : short
    {
        SQL_CB_DELETE   = 0,
        SQL_CB_CLOSE    = 1,
        SQL_CB_PRESERVE = 2,
    }

    public const ushort SQL_FALSE = 0;
    public const ushort SQL_TRUE  = 1;

    public enum DiagHandleType : short
    {
        SQL_HANDLE_ENV  = 1,
        SQL_HANDLE_DBC  = 2,
        SQL_HANDLE_STMT = 3,
        SQL_HANDLE_DESC = 4
    }

    public enum CursorAttributesEnum
    {
        /* supported SQLFetchScroll FetchOrientation's */
        SQL_CA1_NEXT                      = 0x00000001,
        SQL_CA1_ABSOLUTE                  = 0x00000002,
        SQL_CA1_RELATIVE                  = 0x00000004,
        SQL_CA1_BOOKMARK                  = 0x00000008,

        /* supported SQLSetPos LockType's */
        SQL_CA1_LOCK_NO_CHANGE            = 0x00000040,
        SQL_CA1_LOCK_EXCLUSIVE            = 0x00000080,
        SQL_CA1_LOCK_UNLOCK               = 0x00000100,

        /* supported SQLSetPos Operations */
        SQL_CA1_POS_POSITION              = 0x00000200,
        SQL_CA1_POS_UPDATE                = 0x00000400,
        SQL_CA1_POS_DELETE                = 0x00000800,
        SQL_CA1_POS_REFRESH               = 0x00001000,

        /* positioned updates and deletes */
        SQL_CA1_POSITIONED_UPDATE         = 0x00002000,
        SQL_CA1_POSITIONED_DELETE         = 0x00004000,
        SQL_CA1_SELECT_FOR_UPDATE         = 0x00008000,

        /* supported SQLBulkOperations operations */
        SQL_CA1_BULK_ADD                  = 0x00010000,
        SQL_CA1_BULK_UPDATE_BY_BOOKMARK   = 0x00020000,
        SQL_CA1_BULK_DELETE_BY_BOOKMARK   = 0x00040000,
        SQL_CA1_BULK_FETCH_BY_BOOKMARK    = 0x00080000,
    }

    public enum NullableEnum : short
    {
        SQL_NO_NULLS            = 0,
        SQL_NULLABLE            = 1,
        SQL_NULLABLE_UNKNOWN    = 2,
    }

    public enum FieldIdentifierEnum : short
    {
        SQL_COLUMN_COUNT                = 0,
        SQL_COLUMN_NAME                 = 1,
        SQL_COLUMN_TYPE                 = 2,
        SQL_COLUMN_LENGTH               = 3,
        SQL_COLUMN_PRECISION            = 4,
        SQL_COLUMN_SCALE                = 5,
        SQL_COLUMN_DISPLAY_SIZE         = 6,
        SQL_COLUMN_NULLABLE             = 7,
        SQL_COLUMN_UNSIGNED             = 8,
        SQL_COLUMN_MONEY                = 9,
        SQL_COLUMN_UPDATABLE            = 10,
        SQL_COLUMN_AUTO_INCREMENT       = 11,
        SQL_COLUMN_CASE_SENSITIVE       = 12,
        SQL_COLUMN_SEARCHABLE           = 13,
        SQL_COLUMN_TYPE_NAME            = 14,
        SQL_COLUMN_TABLE_NAME           = 15,
        SQL_COLUMN_OWNER_NAME           = 16,
        SQL_COLUMN_QUALIFIER_NAME       = 17,
        SQL_COLUMN_LABEL                = 18,
    }

    public enum ColUpdatableEnum : short
    {
        SQL_ATTR_READONLY = 0,
        SQL_ATTR_WRITE = 1,
        SQL_ATTR_READWRITE_UNKNOWN = 2,
    }

    public enum CursorTypeEnum : int
    {
        SQL_CURSOR_FORWARD_ONLY         = 0,
        SQL_CURSOR_KEYSET_DRIVEN        = 1,
        SQL_CURSOR_DYNAMIC              = 2,
        SQL_CURSOR_STATIC               = 3,
    }
    public const uint DEAD_MAGIC = 0xdeadbeef;

    public enum RowStatusEnum : short
    {
        SQL_ROW_SUCCESS                  = 0,
        SQL_ROW_DELETED                  = 1,
        SQL_ROW_UPDATED                  = 2,
        SQL_ROW_NOROW                    = 3,
        SQL_ROW_ADDED                    = 4,
        SQL_ROW_ERROR                    = 5,
        SQL_ROW_SUCCESS_WITH_INFO        = 6,
    }

    public enum DriverCompletionEnum : ushort
    {
        SQL_DRIVER_NOPROMPT              = 0,
        SQL_DRIVER_COMPLETE              = 1,
        SQL_DRIVER_PROMPT                = 2,
        SQL_DRIVER_COMPLETE_REQUIRED     = 3,
    }

    public enum FreeStmtEnum : ushort 
    {
        SQL_CLOSE           = 0,
        SQL_DROP            = 1,
        SQL_UNBIND          = 2,
        SQL_RESET_PARAMS    = 3,
    }
}

値の読み書き

文字列の書き込みはあちこちで登場するので、こんな感じのメソッドを作りました。

可変長文字列のように、呼び出し元が関数を呼ぶとき、予めどれくらいバッファーを確保すればいいか分からないことがある場合、まず結果を書き込むポインターはNULL にしておき、長さのポインターだけを指定して必要なバッファーサイズを取得し、必要なバッファーを確保してから、改めて呼び出すという使い方をします。

そのため、下記のパターンで呼び出されるので、それぞれに応じて処理を行うようにします。

*valuePtr BufferLength *LengthPtr 説明
0 0 x 呼び出しに必要なバッファーの長さを取得
x x 0 必要なバッファーを渡して文字列を取得
x x x 予め確保したバッファーに書き込んでもらい、書き込まれた長さを取得

なお、今回は手抜きしていますが、実際このような処理を書く場合は、コピーする配列の長さがBufferLength 以下であることをチェックして、相手の用意したバッファーを突き抜けないようにしましょう。

    /// <summary>
    /// ANSI(ASCII)文字列の書き込み
    /// </summary>
    /// <param name="writeValue">書き込む文字</param>
    /// <param name="valuePtr">書き込み先のポインター</param>
    /// <param name="BufferLength">ポインターで確保されているバッファーの長さ</param>
    /// <param name="lengthPtr">長さを返すポインター</param>
    private static void WriteANSIStringToPointer(string writeValue, IntPtr valuePtr, short BufferLength, IntPtr lengthPtr)
    {
        var writeValueBytes = Encoding.ASCII.GetBytes(writeValue);
        Logger.WriteLog(Logger.LogType.Debug, nameof(WriteANSIStringToPointer), $"{writeValue} --{string.Join(' ', writeValueBytes.Select(x => $"{x:X2}"))}({writeValueBytes.Length}),{nameof(valuePtr)}:{valuePtr},{nameof(BufferLength)}:{BufferLength},{nameof(lengthPtr)}:{lengthPtr}");
        if (valuePtr != IntPtr.Zero)
        {
            unsafe
            {
                ref var refwriteValueBytes = ref Unsafe.AsRef<byte>(valuePtr.ToPointer());
                if (0 < BufferLength) Unsafe.InitBlock(ref refwriteValueBytes, 0, (uint)BufferLength);
                if (0 < writeValueBytes.Length) Unsafe.CopyBlock(ref refwriteValueBytes, ref writeValueBytes[0], (uint)writeValueBytes.Length);
            }
        }
        if (lengthPtr != IntPtr.Zero) Marshal.WriteInt16(lengthPtr, (short)(writeValueBytes.Length == 0 ? -1 : writeValueBytes.Length));
    }

    /// <summary>
    /// Unicode文字列の書き込み
    /// </summary>
    /// <param name="writeValue">書き込む文字</param>
    /// <param name="valuePtr">書き込み先のポインター</param>
    /// <param name="BufferLength">ポインターで確保されているバッファーの長さ</param>
    /// <param name="lengthPtr">長さを返すポインター</param>
    private static void WriteUniStringToPointer<T>(string writeValue, IntPtr valuePtr, T BufferLength, IntPtr lengthPtr) where T : struct
    {
        var writeValueBytes = Encoding.Unicode.GetBytes(writeValue);
        Logger.WriteLog(Logger.LogType.Debug, nameof(WriteUniStringToPointer), $"{writeValue} --{string.Join(' ', writeValueBytes.Select(x => $"{x:X2}"))}({writeValueBytes.Length}),{nameof(valuePtr)}:{valuePtr},{nameof(BufferLength)}:{BufferLength}({BufferLength.GetType()}),{nameof(lengthPtr)}:{lengthPtr}");
        var length = BufferLength switch
        {
            short x => (int)x,
            int x => x,
            _ => -1

        };
        if (valuePtr != IntPtr.Zero)
        {
            unsafe
            {
                ref var refwriteValueBytes = ref Unsafe.AsRef<byte>(valuePtr.ToPointer());
                if (0 < length) Unsafe.InitBlock(ref refwriteValueBytes, 0, (uint)length);
                if (0 < writeValueBytes.Length) Unsafe.CopyBlock(ref refwriteValueBytes, ref writeValueBytes[0], (uint)writeValueBytes.Length);
            }
        }
        if (lengthPtr != IntPtr.Zero)
        {
            switch (BufferLength)
            {
                case short x:
                    Marshal.WriteInt16(lengthPtr, (short)(writeValueBytes.Length == 0 ? -1 : writeValueBytes.Length));
                    break;
                default:
                    Marshal.WriteInt32(lengthPtr, (writeValueBytes.Length == 0 ? -1 : writeValueBytes.Length));
                    break;
            }
        }
    }

データを保持するクラス

結果を返すために、Col やRow などの情報を保持したりするクラスです。

    private class Dataset
    {
        public Dictionary<int, ColItem> Cols = [];
        public Dictionary<int, Dictionary<int, object>> Rows = [];
        public int CurrentRow = 0;
        public void MoveNext() => CurrentRow++;
        public bool Fetch()
        {
            if (!Rows.TryGetValue(CurrentRow, out var row))
            {
                Logger.WriteLog(Logger.LogType.Information, nameof(Fetch), $"{nameof(CurrentRow)}:{CurrentRow} not found.");
                return false;
            }

            foreach (var col in Cols)
            {
                if (!row.TryGetValue(col.Key, out var value)) continue;
                switch (col.Value.TargetType)
                {
                case SQLTypeEnum.SQL_CHAR:

                    WriteANSIStringToPointer(value?.ToString() ?? string.Empty, col.Value.TargetValuePtr,col.Value.BufferLength, col.Value.StrLen_or_IndPtr);
                    break;
                case SQLTypeEnum.SQL_WCHAR:
                    WriteUniStringToPointer(value?.ToString() ?? string.Empty, col.Value.TargetValuePtr, col.Value.BufferLength, col.Value.StrLen_or_IndPtr);
                    break;
                case SQLTypeEnum.SQL_WVARCHAR:
                    WriteUniStringToPointer(value?.ToString() ?? string.Empty, col.Value.TargetValuePtr, col.Value.BufferLength, col.Value.StrLen_or_IndPtr);
                    break;
                case SQLTypeEnum.SQL_WLONGVARCHAR:
                    break;
                default:
                    break;
                }
            }
            return true;
        }

    }

    private class ColItem
    {
        public string Name { get; set; } = string.Empty;
        public SQLTypeEnum TargetType { get; set; }
        public SQLTypeEnum DataTypeCode { get; set; }
        public int ColumnSize { get; set; }
        public int DecimalDigits { get; set; }
        public NullableEnum Nullable { get; set; }
        public IntPtr TargetValuePtr { get; set; }
        public bool IsUpdatable { get; set; }
        public short BufferLength { get; set; }
        public IntPtr StrLen_or_IndPtr { get; set; }
    }
    
    private static readonly Dictionary<IntPtr, Dataset> Resultset = [];

関数解説

SQLAllocHandle

SQLAllocEnv に相当します。

ハンドル割り当てる系は、シンプルにインクリメントした値を返すだけです。

    private static int AllockHandle = 0;
    /// <summary>
    /// 環境、接続、ステートメント、または記述子ハンドルを割り当てます
    /// </summary>
    /// <param name="HandleType">[IN] SQLAllocHandle によって割り当てられるハンドルの種類</param>
    /// <param name="InputHandle">[IN] 新しいハンドルが割り当てられるコンテキスト内の入力ハンドル</param>
    /// <param name="OutputHandlePtr">[OUT] 新しく割り当てられたデータ構造にハンドルを返すバッファーへのポインター</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_INVALID_HANDLE、またはSQL_ERROR</returns>
    /// <example>SQLRETURN SQLAllocHandle(SQLSMALLINT HandleType, SQLHANDLE InputHandle, SQLHANDLE * OutputHandlePtr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlallochandle-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLAllocHandle))]
    public static SQLRETURN SQLAllocHandle(DiagHandleType HandleType, IntPtr InputHandle, IntPtr OutputHandlePtr)
    {
        Marshal.WriteInt32(OutputHandlePtr, AllockHandle++);
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLAllocHandle), $"{nameof(HandleType)}:{HandleType},{nameof(InputHandle)}:{InputHandle},{nameof(OutputHandlePtr)}:{OutputHandlePtr}({AllockHandle})");
        return SQLRETURN.SQL_SUCCESS;
    }

SQLGetEnvAttr

特に何もしてなくて、SQL_SUCCESS を返しているだけです。

    /// <summary>
    /// 環境属性の現在の設定を返します
    /// </summary>
    /// <param name="EnvironmentHandle">[入力] 環境ハンドル</param>
    /// <param name="Attribute">[入力] 取得する属性</param>
    /// <param name="ValuePtr">[出力] Attribute で指定された属性の現在の値を返すバッファーへのポインター</param>
    /// <param name="BufferLength">[入力] ValuePtr が 文字列を指す場合、この引数は *ValuePtr の長さである必要があります</param>
    /// <param name="StringLengthPtr">[出力] *ValuePtr で返すことができる合計バイト数 (null 終端文字を除く) を返すバッファーへのポインター</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_NO_DATA、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLGetEnvAttr(SQLHENV EnvironmentHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER BufferLength, SQLINTEGER * StringLengthPtr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlgetenvattr-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLGetEnvAttr))]
    public static SQLRETURN SQLGetEnvAttr(IntPtr EnvironmentHandle, AttributeEnum Attribute, IntPtr ValuePtr, int BufferLength, IntPtr StringLengthPtr)
    {
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLGetEnvAttr), $"{nameof(EnvironmentHandle)}:{EnvironmentHandle},{nameof(Attribute)}:{Attribute},{nameof(ValuePtr)}:{ValuePtr},{nameof(BufferLength)}:{BufferLength},{nameof(BufferLength)}:{BufferLength},{nameof(StringLengthPtr)}:{StringLengthPtr}");
        return SQLRETURN.SQL_SUCCESS;
    }

SQLSetEnvAttr

ここで重要なのは、SQL_ATTR_ODBC_VERSION で指定されるODBC のバージョンが、2 なのか、3 なのかというところです。

    /// <summary>
    /// 環境の側面を制御する属性を設定します
    /// </summary>
    /// <param name="EnvironmentHandle">環境ハンドル</param>
    /// <param name="Attribute">設定する属性</param>
    /// <param name="ValuePtr">Attribute に関連付ける値へのポインター</param>
    /// <param name="StringLength">ValuePtr が 文字列またはバイナリ バッファーを指している場合、この引数は *ValuePtr の長さである必要があります</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLSetEnvAttr(SQLHENV EnvironmentHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlsetenvattr-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLSetEnvAttr))]
    public static SQLRETURN SQLSetEnvAttr(IntPtr EnvironmentHandle, AttributeEnum Attribute, IntPtr ValuePtr, int StringLength)
    {
        var value = Attribute switch
        {
            AttributeEnum.SQL_ATTR_ODBC_VERSION => ((AttrODBCVersion)ValuePtr).ToString(),
            AttributeEnum.SQL_ATTR_CONNECTION_POOLING => ((AttrConnectionPoolingEnum)ValuePtr).ToString(),
            AttributeEnum.SQL_ATTR_CP_MATCH => ((AttrConnectionPoolingMatchEnum)ValuePtr).ToString(),
            AttributeEnum.SQL_ATTR_OUTPUT_NTS => ValuePtr.ToString(),
            _ => Marshal.PtrToStringUni(ValuePtr)
        };

        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLSetEnvAttr), $"{nameof(EnvironmentHandle)}:{EnvironmentHandle},{nameof(Attribute)}:{Attribute},{nameof(value)}:{value}({StringLength})");
        return SQLRETURN.SQL_SUCCESS;
    }

SQLAllocHandle

SQLAllocConnect とSQLAllocStmt に相当します。

こちらもインクリメントした値を返すだけ

    /// <summary>
    /// 環境、接続、ステートメント、または記述子ハンドルを割り当てます
    /// </summary>
    /// <param name="HandleType">[IN] SQLAllocHandle によって割り当てられるハンドルの種類</param>
    /// <param name="InputHandle">[IN] 新しいハンドルが割り当てられるコンテキスト内の入力ハンドル</param>
    /// <param name="OutputHandlePtr">[OUT] 新しく割り当てられたデータ構造にハンドルを返すバッファーへのポインター</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_INVALID_HANDLE、またはSQL_ERROR</returns>
    /// <example>SQLRETURN SQLAllocHandle(SQLSMALLINT HandleType, SQLHANDLE InputHandle, SQLHANDLE * OutputHandlePtr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlallochandle-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLAllocHandle))]
    public static SQLRETURN SQLAllocHandle(DiagHandleType HandleType, IntPtr InputHandle, IntPtr OutputHandlePtr)
    {
        Marshal.WriteInt32(OutputHandlePtr, AllockHandle++);
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLAllocHandle), $"{nameof(HandleType)}:{HandleType},{nameof(InputHandle)}:{InputHandle},{nameof(OutputHandlePtr)}:{OutputHandlePtr}({AllockHandle})");
        return SQLRETURN.SQL_SUCCESS;
    }

SQLGetInfoW

各種情報を返します。
ここは、SQLite を参考にそれらしいデータを返すようにしています。

    private const string DriverName = "CSharpODBC.dll";
    private const string DBMSName = "SQLite";
    private const string DBMSVersion = "3.43.2";
    private const string DatabaseName = "TestDB";
    private const string DriverVersion = "03.00";
    private const string ODBCVersion = "03.00";

    /// <summary>
    /// 接続に関連付けられているドライバーとデータ ソースに関する一般的な情報を返します
    /// </summary>
    /// <param name="ConnectionHandle">[入力] 接続ハンドル</param>
    /// <param name="InfoType">[入力] 情報の種類</param>
    /// <param name="InfoValuePtr">[出力] 情報を返すバッファーへのポインター</param>
    /// <param name="BufferLength">[入力] *InfoValuePtr バッファーの長さ</param>
    /// <param name="StringLengthPtr">[出力] *InfoValuePtr で返すことができる合計バイト数 (文字データの null 終端文字を除く) を返すバッファーへのポインター</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLGetInfo(SQLHDBC ConnectionHandle, SQLUSMALLINT InfoType, SQLPOINTER InfoValuePtr, SQLSMALLINT BufferLength, SQLSMALLINT* StringLengthPtr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlgetinfo-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLGetInfoW))]
    public static SQLRETURN SQLGetInfoW(IntPtr ConnectionHandle, InfoTypeEnum InfoType, IntPtr InfoValuePtr, short BufferLength, IntPtr StringLengthPtr)
    {
        static void writeValue(IntPtr infoValuePtr, short length, int value)
        {
            switch (length)
            {
                case 2:
                    Marshal.WriteInt16(infoValuePtr, (short)value);
                    break;
                case 4:
                    Marshal.WriteInt32(infoValuePtr, value);
                    break;
                default :
                    Logger.WriteLog(Logger.LogType.Debug, $"{nameof(SQLGetInfoW)}_{nameof(writeValue)}", $"{nameof(infoValuePtr)}:{infoValuePtr},{nameof(length)}:{length},{nameof(value)}:{value}");
                    break;
            }
        }
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLGetInfoW), $"{nameof(ConnectionHandle)}:{ConnectionHandle},{nameof(InfoType)}:{InfoType},{nameof(InfoValuePtr)}:{InfoValuePtr},{nameof(BufferLength)}:{BufferLength},{nameof(StringLengthPtr)}:{StringLengthPtr}");
        switch (InfoType)
        {
            case InfoTypeEnum.SQL_ACTIVE_STATEMENTS:
                writeValue(InfoValuePtr, BufferLength, 1);
                break;
            case InfoTypeEnum.SQL_FETCH_DIRECTION:
                writeValue(InfoValuePtr, BufferLength, (int)(SQL_FD_FETCHEnum.SQL_FD_FETCH_NEXT | SQL_FD_FETCHEnum.SQL_FD_FETCH_FIRST | SQL_FD_FETCHEnum.SQL_FD_FETCH_LAST | SQL_FD_FETCHEnum.SQL_FD_FETCH_PRIOR | SQL_FD_FETCHEnum.SQL_FD_FETCH_ABSOLUTE));
                break;
            case InfoTypeEnum.SQL_ODBC_VER:
                WriteUniStringToPointer(ODBCVersion, InfoValuePtr, BufferLength, StringLengthPtr);
                break;
            case InfoTypeEnum.SQL_SEARCH_PATTERN_ESCAPE:
                WriteUniStringToPointer("\\", InfoValuePtr, BufferLength, StringLengthPtr);
                break;
            case InfoTypeEnum.SQL_DATABASE_NAME:
                WriteUniStringToPointer(DatabaseName, InfoValuePtr, BufferLength, StringLengthPtr);
                break;
            case InfoTypeEnum.SQL_DBMS_NAME:
                WriteUniStringToPointer(DBMSName, InfoValuePtr, BufferLength, StringLengthPtr);
                break;
            case InfoTypeEnum.SQL_DBMS_VER:
                WriteUniStringToPointer(DBMSVersion, InfoValuePtr, BufferLength, StringLengthPtr);
                break;
            case InfoTypeEnum.SQL_CURSOR_COMMIT_BEHAVIOR:
                writeValue(InfoValuePtr, BufferLength, (short)SQL_CURSOR_COMMIT_BEHAVIOR_Enum.SQL_CB_PRESERVE);
                break;
            case InfoTypeEnum.SQL_CURSOR_ROLLBACK_BEHAVIOR:
                writeValue(InfoValuePtr, BufferLength, (short)(SQL_CBEnum.SQL_CB_PRESERVE));
                break;
            case InfoTypeEnum.SQL_DEFAULT_TXN_ISOLATION:
                writeValue(InfoValuePtr, BufferLength, (int)(SQL_TXN.SQL_TXN_SERIALIZABLE));
                break;
            case InfoTypeEnum.SQL_IDENTIFIER_QUOTE_CHAR:
                WriteUniStringToPointer("\"", InfoValuePtr, BufferLength, StringLengthPtr);
                break;
            case InfoTypeEnum.SQL_MAX_COLUMN_NAME_LEN:
            case InfoTypeEnum.SQL_MAX_OWNER_NAME_LEN:
            case InfoTypeEnum.SQL_MAX_PROCEDURE_NAME_LEN:
            case InfoTypeEnum.SQL_MAX_QUALIFIER_NAME_LEN:
            case InfoTypeEnum.SQL_MAX_TABLE_NAME_LEN:
                writeValue(InfoValuePtr, BufferLength, 255);
                break;
            case InfoTypeEnum.SQL_OWNER_TERM:
                WriteUniStringToPointer("", InfoValuePtr, BufferLength, StringLengthPtr);
                break;
            case InfoTypeEnum.SQL_PROCEDURE_TERM:
                WriteUniStringToPointer("PROCEDURE", InfoValuePtr, BufferLength, StringLengthPtr);
                break;
            case InfoTypeEnum.SQL_QUALIFIER_TERM:
                WriteUniStringToPointer("catalog", InfoValuePtr, BufferLength, StringLengthPtr);
                break;
            case InfoTypeEnum.SQL_QUALIFIER_NAME_SEPARATOR:
                WriteUniStringToPointer(".", InfoValuePtr, BufferLength, StringLengthPtr);
                break;
            case InfoTypeEnum.SQL_SCROLL_CONCURRENCY:
                writeValue(InfoValuePtr, BufferLength, (int)(SQL_SCCOEnum.SQL_SCCO_LOCK));
                break;
            case InfoTypeEnum.SQL_SCROLL_OPTIONS:
                writeValue(InfoValuePtr, BufferLength, (int)(SQL_SOEnum.SQL_SO_STATIC | SQL_SOEnum.SQL_SO_FORWARD_ONLY));
                break;
            case InfoTypeEnum.SQL_TABLE_TERM:
                WriteUniStringToPointer("TABLE", InfoValuePtr, BufferLength, StringLengthPtr);
                break;
            case InfoTypeEnum.SQL_TXN_CAPABLE:
                writeValue(InfoValuePtr, BufferLength, (short)(SQL_TCEnum.SQL_TC_ALL));
                break;
            case InfoTypeEnum.SQL_TXN_ISOLATION_OPTION:
                writeValue(InfoValuePtr, BufferLength, (int)(SQL_TXN.SQL_TXN_SERIALIZABLE));
                break;
            case InfoTypeEnum.SQL_DRIVER_ODBC_VER:
                WriteUniStringToPointer(DriverVersion, InfoValuePtr, BufferLength, StringLengthPtr);
                break;
            case InfoTypeEnum.SQL_LOCK_TYPES:
                writeValue(InfoValuePtr, BufferLength, (int)SQL_LOCKEnum.SQL_LCK_NO_CHANGE);
                break;
            case InfoTypeEnum.SQL_POS_OPERATIONS:
                writeValue(InfoValuePtr, BufferLength, (int)(SQL_POS_POSITIONEnum.SQL_POS_POSITION | SQL_POS_POSITIONEnum.SQL_POS_UPDATE | SQL_POS_POSITIONEnum.SQL_POS_DELETE | SQL_POS_POSITIONEnum.SQL_POS_ADD | SQL_POS_POSITIONEnum.SQL_POS_REFRESH));
                break;
            case InfoTypeEnum.SQL_POSITIONED_STATEMENTS:
                writeValue(InfoValuePtr, BufferLength, 0);
                break;
            case InfoTypeEnum.SQL_GETDATA_EXTENSIONS:
                writeValue(InfoValuePtr, BufferLength, (int)(SQL_GDEnum.SQL_GD_ANY_COLUMN | SQL_GDEnum.SQL_GD_ANY_ORDER | SQL_GDEnum.SQL_GD_BOUND));
                break;
            case InfoTypeEnum.SQL_BOOKMARK_PERSISTENCE:
                writeValue(InfoValuePtr, BufferLength, (int)(SQL_BPEnum.SQL_BP_SCROLL));
                break;
            case InfoTypeEnum.SQL_STATIC_SENSITIVITY:
                writeValue(InfoValuePtr, BufferLength, 0);
                break;
            case InfoTypeEnum.SQL_QUOTED_IDENTIFIER_CASE:
                writeValue(InfoValuePtr, BufferLength, (int)SQL_ICEnum.SQL_IC_SENSITIVE);
                break;
            case InfoTypeEnum.SQL_DRIVER_NAME:
                WriteUniStringToPointer(DriverName, InfoValuePtr, BufferLength, StringLengthPtr);
                break;
            case InfoTypeEnum.SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1:
                writeValue(InfoValuePtr, BufferLength, (int)(CursorAttributesEnum.SQL_CA1_NEXT | CursorAttributesEnum.SQL_CA1_BOOKMARK));
                break;
            case InfoTypeEnum.SQL_DYNAMIC_CURSOR_ATTRIBUTES1:
            case InfoTypeEnum.SQL_KEYSET_CURSOR_ATTRIBUTES1:
            case InfoTypeEnum.SQL_KEYSET_CURSOR_ATTRIBUTES2:
            case InfoTypeEnum.SQL_STATIC_CURSOR_ATTRIBUTES1:
            case InfoTypeEnum.SQL_STATIC_CURSOR_ATTRIBUTES2:
                writeValue(InfoValuePtr, BufferLength, 0);
                break;
            case InfoTypeEnum.SQL_COLUMN_ALIAS:
            case InfoTypeEnum.SQL_NEED_LONG_DATA_LEN:
            case InfoTypeEnum.SQL_OUTER_JOINS:
                WriteUniStringToPointer("Y", InfoValuePtr, BufferLength, StringLengthPtr);
                //Marshal.WriteIntPtr(InfoValuePtr, Marshal.StringToCoTaskMemUni("Y"));
                break;
            case InfoTypeEnum.SQL_ROW_UPDATES:
            case InfoTypeEnum.SQL_ACCESSIBLE_PROCEDURES:
            case InfoTypeEnum.SQL_PROCEDURES:
            case InfoTypeEnum.SQL_EXPRESSIONS_IN_ORDERBY:
            case InfoTypeEnum.SQL_ODBC_SQL_OPT_IEF:
            case InfoTypeEnum.SQL_LIKE_ESCAPE_CLAUSE:
            case InfoTypeEnum.SQL_ORDER_BY_COLUMNS_IN_SELECT:
            case InfoTypeEnum.SQL_ACCESSIBLE_TABLES:
            case InfoTypeEnum.SQL_MULT_RESULT_SETS:
            case InfoTypeEnum.SQL_MULTIPLE_ACTIVE_TXN:
            case InfoTypeEnum.SQL_MAX_ROW_SIZE_INCLUDES_LONG:
                WriteUniStringToPointer("N", InfoValuePtr, BufferLength, StringLengthPtr);
                //Marshal.WriteIntPtr(InfoValuePtr, Marshal.StringToCoTaskMemUni("N"));
                break;

            default:
                Logger.WriteLog(Logger.LogType.Debug, nameof(SQLGetInfoW), $"(ERROR){nameof(ConnectionHandle)}:{ConnectionHandle},{nameof(InfoType)}:{InfoType},{nameof(InfoValuePtr)}:{InfoValuePtr},{nameof(BufferLength)}:{BufferLength},{nameof(StringLengthPtr)}:{StringLengthPtr}");
                return SQLRETURN.SQL_ERROR;
        }
        return SQLRETURN.SQL_SUCCESS;
    }

SQLSetConnectAttrW

SQL_ATTR_MAX_ROWS のとき、SQL_SUCCESS_WITH_INFO を返していますが、これは、ドライバーがValuePtr で指定された値をサポートしていないことを表します。

    /// <summary>
    /// 接続の側面を制御する属性を設定します
    /// </summary>
    /// <param name="ConnectionHandle">[入力] 接続ハンドル</param>
    /// <param name="Attribute">[入力] [コメント] に表示される、設定する属性</param>
    /// <param name="ValuePtr">[入力] Attribute に関連付ける値へのポインター。 Attribute の値に応じて、ValuePtr は符号なし整数値であるか、null で終わる文字列を指します。</param>
    /// <param name="StringLength">[入力] Attribute が ODBC で定義された属性で、 ValuePtr が文字列またはバイナリ バッファーを指している場合、この引数の長さは *ValuePtr にする必要があります。</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_ERROR、SQL_INVALID_HANDLE、またはSQL_STILL_EXECUTING</returns>
    /// <example>SQLRETURN SQLSetConnectAttr(SQLHDBC ConnectionHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlsetconnectattr-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLSetConnectAttrW))]
    public static SQLRETURN SQLSetConnectAttrW(IntPtr ConnectionHandle, long Attribute, IntPtr ValuePtr, long StringLength)
    {
        SQLRETURN result = SQLRETURN.SQL_SUCCESS;
        switch ((AttributeEnum)Attribute)
        {
            case AttributeEnum.SQL_ATTR_QUERY_TIMEOUT:
                break;
            case AttributeEnum.SQL_ATTR_MAX_ROWS:
                result = SQLRETURN.SQL_SUCCESS_WITH_INFO;
                break;
            case AttributeEnum.SQL_ATTR_ACCESS_MODE:
                break;
            case AttributeEnum.SQL_ATTR_ASYNC_DBC_FUNCTIONS_ENABLE:
                break;
            case AttributeEnum.SQL_ATTR_AUTOCOMMIT:
                break;
            case AttributeEnum.SQL_ATTR_CONNECTION_DEAD:
                break;
            case AttributeEnum.SQL_ATTR_CONNECTION_TIMEOUT:
                break;
            case AttributeEnum.SQL_ATTR_CURRENT_CATALOG:
                break;
            case AttributeEnum.SQL_ATTR_ENLIST_IN_DTC:
                break;
            case AttributeEnum.SQL_ATTR_LOGIN_TIMEOUT:
                break;
            case AttributeEnum.SQL_ATTR_ODBC_CURSORS:
                break;
            case AttributeEnum.SQL_ATTR_PACKET_SIZE:
                break;
            case AttributeEnum.SQL_ATTR_QUIET_MODE:
                break;
            case AttributeEnum.SQL_ATTR_TRACE:
                break;
            case AttributeEnum.SQL_ATTR_TRACEFILE:
                break;
            case AttributeEnum.SQL_ATTR_TRANSLATE_LIB:
                break;
            case AttributeEnum.SQL_ATTR_TRANSLATE_OPTION:
                break;
            case AttributeEnum.SQL_ATTR_TXN_ISOLATION:
                break;
            case AttributeEnum.SQL_ATTR_ODBC_VERSION:
                break;
            default:
                break;
        }
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLSetConnectAttrW), $"{nameof(ConnectionHandle)}:{ConnectionHandle},{nameof(Attribute)}:{Attribute},{nameof(ValuePtr)}:{ValuePtr}({StringLength})");
        return result;
    }

SQLDriverConnectW

引数で渡された文字列をそのまま返しています。

    /// <summary>
    /// SQLConnect の代替手段です。 SQLConnect の 3 つの引数よりも多くの接続情報を必要とするデータ ソース、すべての接続情報の入力をユーザーに求めるダイアログ ボックス、およびシステム情報で定義されていないデータ ソースがサポートされています
    /// </summary>
    /// <param name="ConnectionHandle">[入力] 接続ハンドル</param>
    /// <param name="WindowHandle">[入力] ウィンドウ ハンドル</param>
    /// <param name="InConnectionString">[入力] 完全な接続文字列 (「コメント」の構文を参照)、部分的な接続文字列、または空の文字列</param>
    /// <param name="StringLength1">[入力] 文字列が Unicode の場合は *InConnectionString、文字列が ANSI または DBCS の場合はバイト単位の長さ</param>
    /// <param name="OutConnectionString">[出力] 完了した接続文字列のバッファーへのポインター</param>
    /// <param name="BufferLength">[入力] *OutConnectionString バッファーの長さ (文字数)</param>
    /// <param name="StringLength2Ptr">[出力] *OutConnectionString で返すために使用できる文字数 (null 終了文字を除く) の合計数を返すバッファーへのポインター</param>
    /// <param name="DriverCompletion">[入力] ドライバー マネージャーまたはドライバーがより多くの接続情報を求める必要があるかどうかを示すフラグ</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_NO_DATA、SQL_ERROR、SQL_INVALID_HANDLE、またはSQL_STILL_EXECUTING</returns>
    /// <example>SQLRETURN SQLDriverConnect(SQLHDBC ConnectionHandle, SQLHWND WindowHandle, SQLCHAR * InConnectionString, SQLSMALLINT StringLength1, SQLCHAR * OutConnectionString, SQLSMALLINT BufferLength, SQLSMALLINT * StringLength2Ptr, SQLUSMALLINT DriverCompletion);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqldriverconnect-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLDriverConnectW))]
    public static SQLRETURN SQLDriverConnectW(IntPtr ConnectionHandle, IntPtr WindowHandle, IntPtr InConnectionString, short StringLength1, IntPtr OutConnectionString, short BufferLength, IntPtr StringLength2Ptr, DriverCompletionEnum DriverCompletion)
    {
        var inConnectionString = Marshal.PtrToStringUni(InConnectionString) ?? string.Empty;
        WriteUniStringToPointer(inConnectionString, OutConnectionString, BufferLength, StringLength2Ptr);
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLDriverConnectW), $"{nameof(ConnectionHandle)}:{ConnectionHandle},{nameof(WindowHandle)}:{WindowHandle},{nameof(inConnectionString)}:{inConnectionString}({StringLength1}),{nameof(OutConnectionString)}:{OutConnectionString}({BufferLength}),{nameof(StringLength2Ptr)}:{StringLength2Ptr},{nameof(DriverCompletion)}:{DriverCompletion}");
        return SQLRETURN.SQL_SUCCESS;
    }

SQLGetFunctions

SQLGetInfoW と並んで重要なのが、このSQLGetFunctions です。
この関数は、ドライバーがサポートしている関数を返します。
 
ODBC ドライバーの関数には、それぞれ番号が振られていて、アプリケーションが、ドライバーのサポートしている関数を知りたいとき、個別に番号を指定して、その関数に対応しているかをYes(SQL_TRUE)、or No(SQL_FALSE) で取得する方法と、関数ごとの対応の有無を、配列で一括取得する方法があります。
そして、一括取得する方法では、ODBC バージョンが2と3で異なり、2ではSQL_API_ALL_FUNCTIONS(=0) 、3ならSQL_API_ODBC3_ALL_FUNCTIONS(=999) が使われます。
SQL_API_ALL_FUNCTIONS の場合、100個の配列にそれぞれ対応の有無を入れて返します。
SQL_API_ODBC3_ALL_FUNCTIONS の場合、配列サイズが250個に拡張され、かつ全体が4000bit のフラグとして扱われるようになります。
つまり、返せる関数の情報が100個→4000個に拡張されているわけです。

    /// <summary>
    /// ドライバーが特定の ODBC 関数をサポートしているかどうかに関する情報を返します
    /// </summary>
    /// <param name="ConnectionHandle">[入力] 接続ハンドル</param>
    /// <param name="FunctionId">[入力]目的の ODBC 関数を識別する</param>
    /// <param name="SupportedPtr">[出力] FunctionId が 1 つの ODBC 関数を識別する場合、 SupportedPtr は、指定した関数がドライバーでサポートされている場合はSQL_TRUEされる単一の SQLUSMALLINT 値を指し、サポートされていない場合はSQL_FALSE
    /// FunctionId がSQL_API_ODBC3_ALL_FUNCTIONSの場合、SupportedPtr は、SQL_API_ODBC3_ALL_FUNCTIONS_SIZEと等しい要素の数を持つ SQLSMALLINT 配列を指します。
    /// FunctionId がSQL_API_ALL_FUNCTIONSの場合、SupportedPtr は 100 個の要素の SQLUSMALLINT 配列を指します。 </param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLGetFunctions(SQLHDBC ConnectionHandle, SQLUSMALLINT FunctionId, SQLUSMALLINT * SupportedPtr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlgetfunctions-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLGetFunctions))]
    public static SQLRETURN SQLGetFunctions(IntPtr ConnectionHandle, FunctionsEnum FunctionId, IntPtr SupportedPtr)
    {
        var exists = new ushort[100];
        switch (FunctionId)
        {
            case FunctionsEnum.SQL_API_ALL_FUNCTIONS:
                exists[(int)FunctionsEnum.SQL_API_SQLALLOCCONNECT] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLFETCH] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLALLOCENV] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLFREECONNECT] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLALLOCSTMT] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLFREEENV] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLBINDCOL] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLFREESTMT] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLCANCEL] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLGETCURSORNAME] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLCOLATTRIBUTE] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLNUMRESULTCOLS] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLCONNECT] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLPREPARE] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLDESCRIBECOL] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLROWCOUNT] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLDISCONNECT] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLSETCURSORNAME] = SQL_FALSE;
                exists[(int)FunctionsEnum.SQL_API_SQLERROR] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLSETPARAM] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLEXECDIRECT] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLTRANSACT] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLBULKOPERATIONS] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLEXECUTE] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLBINDPARAMETER] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLGETTYPEINFO] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLCOLUMNS] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLPARAMDATA] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLDRIVERCONNECT] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLPUTDATA] = SQL_FALSE;
                exists[(int)FunctionsEnum.SQL_API_SQLGETCONNECTOPTION] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLSETCONNECTOPTION] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLGETDATA] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLSETSTMTOPTION] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLGETFUNCTIONS] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLSPECIALCOLUMNS] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLGETINFO] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLSTATISTICS] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLGETSTMTOPTION] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLTABLES] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLBROWSECONNECT] = SQL_FALSE;
                exists[(int)FunctionsEnum.SQL_API_SQLNUMPARAMS] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLCOLUMNPRIVILEGES] = SQL_FALSE;
                exists[(int)FunctionsEnum.SQL_API_SQLPARAMOPTIONS] = SQL_FALSE;
                exists[(int)FunctionsEnum.SQL_API_SQLDATASOURCES] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLPRIMARYKEYS] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLDESCRIBEPARAM] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLPROCEDURECOLUMNS] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLDRIVERS] = SQL_FALSE;
                exists[(int)FunctionsEnum.SQL_API_SQLPROCEDURES] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLEXTENDEDFETCH] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLSETPOS] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLFOREIGNKEYS] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLSETSCROLLOPTIONS] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLMORERESULTS] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLTABLEPRIVILEGES] = SQL_TRUE;
                exists[(int)FunctionsEnum.SQL_API_SQLNATIVESQL] = SQL_TRUE;
                break;
            case FunctionsEnum.SQL_API_ODBC3_ALL_FUNCTIONS:
                exists = new ushort[250];
                exists[0] = 0xfffe; //b1111111111111110
                exists[1] = 0x01df; //b0000000111011111
                exists[2] = 0xff00; //b1111111100000000
                exists[3] = 0xfe7f; //b1111111001111111
                exists[4] = 0x03ff; //b0000001111111111
                exists[62] = 0xee00;//b1110111000000000
                exists[63] = 0x395c;//b0011100101011100
                exists[96] = 0x8000;//b1000000000000000
                break;
            default:
                break;
        }
        unsafe
        {
            Unsafe.CopyBlock(SupportedPtr.ToPointer(), Unsafe.AsPointer(ref exists[0]), (uint)exists.Length);
        }
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLGetFunctions), $"{nameof(ConnectionHandle)}:{ConnectionHandle},{nameof(FunctionId)}:{FunctionId},{nameof(SupportedPtr)}:{SupportedPtr}");
        return SQLRETURN.SQL_SUCCESS;
    }

SQLGetConnectAttrW

特に返すデータはないので、SQL_NO_DATA を返しています。

    /// <summary>
    /// 接続属性の現在の設定を返します
    /// </summary>
    /// <param name="ConnectionHandle">[入力] 接続ハンドル</param>
    /// <param name="Attribute">[入力] 取得する属性</param>
    /// <param name="ValuePtr">[出力] Attribute で指定された属性の現在の値を返すメモリへのポインター</param>
    /// <param name="BufferLength">[入力] Attribute が ODBC 定義の属性で 、ValuePtr が文字列またはバイナリ バッファーを指している場合、この引数は *ValuePtr の長さである必要があります</param>
    /// <param name="StringLengthPtr">[出力] *ValuePtr で返すために使用できるバイトの合計数 (null 終了文字を除く) を返すバッファーへのポインター</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_NO_DATA、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLGetConnectAttr(SQLHDBC ConnectionHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER BufferLength, SQLINTEGER * StringLengthPtr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlgetconnectattr-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLGetConnectAttrW))]
    public static SQLRETURN SQLGetConnectAttrW(IntPtr ConnectionHandle, int Attribute, IntPtr ValuePtr, int BufferLength, IntPtr StringLengthPtr)
    {
        var value = Marshal.PtrToStringUni(ValuePtr);
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLGetConnectAttrW), $"{nameof(ConnectionHandle)}:{ConnectionHandle},{nameof(Attribute)}:{Attribute},{nameof(ValuePtr)}:{value},{nameof(BufferLength)}:{BufferLength},{nameof(StringLengthPtr)}:{StringLengthPtr}");
        return SQLRETURN.SQL_NO_DATA;
    }

SQLSetStmtAttrW

何かをする必要はありませんが、一応、Attribute に未知の値が指定されたときはError になるようにしておきます。

    /// <summary>
    /// ステートメントに関連する属性を設定します
    /// </summary>
    /// <param name="StatementHandle">[入力] ステートメント ハンドル</param>
    /// <param name="Attribute">[入力] [コメント] に表示される、設定するオプション</param>
    /// <param name="ValuePtr">[入力] Attribute に関連付ける値。 Attribute の値に応じて、ValuePtr は次のいずれかになります
    /// - null で終わる文字列
    /// - バイナリ バッファー
    /// - SQLLEN、SQLULEN、または SQLUSMALLINT 型の値または配列
    /// - ドライバー定義の値</param>
    /// <param name="StringLength">[入力] Attribute が ODBC で定義された属性で、 ValuePtr が文字列またはバイナリ バッファーを指している場合、この引数の長さは *ValuePtr にする必要があります</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLSetStmtAttr(SQLHSTMT StatementHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlsetstmtattr-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLSetStmtAttrW))]
    public static SQLRETURN SQLSetStmtAttrW(IntPtr StatementHandle, StatmentAttributesEnum Attribute, IntPtr ValuePtr, int StringLength)
    {
        switch (Attribute)
        {
            case StatmentAttributesEnum.SQL_ATTR_QUERY_TIMEOUT:
                break;
            case StatmentAttributesEnum.SQL_ATTR_MAX_ROWS:
                break;
            case StatmentAttributesEnum.SQL_ATTR_ROW_BIND_TYPE:
                break;
            case StatmentAttributesEnum.SQL_ATTR_CURSOR_TYPE:
                break;
            case StatmentAttributesEnum.SQL_ATTR_CONCURRENCY:
                break;
            case StatmentAttributesEnum.SQL_ROWSET_SIZE:
                break;
            case StatmentAttributesEnum.SQL_ATTR_SIMULATE_CURSOR:
                break;
            case StatmentAttributesEnum.SQL_ATTR_RETRIEVE_DATA:
                break;
            case StatmentAttributesEnum.SQL_ATTR_USE_BOOKMARKS:
                break;
            case StatmentAttributesEnum.SQL_ATTR_ROW_NUMBER:
                break;
            case StatmentAttributesEnum.SQL_ATTR_PARAM_BIND_OFFSET_PTR:
                break;
            case StatmentAttributesEnum.SQL_ATTR_PARAM_BIND_TYPE:
                break;
            case StatmentAttributesEnum.SQL_ATTR_PARAM_OPERATION_PTR:
                break;
            case StatmentAttributesEnum.SQL_ATTR_PARAM_STATUS_PTR:
                break;
            case StatmentAttributesEnum.SQL_ATTR_PARAMS_PROCESSED_PTR:
                break;
            case StatmentAttributesEnum.SQL_ATTR_PARAMSET_SIZE:
                break;
            case StatmentAttributesEnum.SQL_ATTR_ROW_BIND_OFFSET_PTR:
                break;
            case StatmentAttributesEnum.SQL_ATTR_ROW_OPERATION_PTR:
                break;
            case StatmentAttributesEnum.SQL_ATTR_ROW_STATUS_PTR:
                break;
            case StatmentAttributesEnum.SQL_ATTR_ROWS_FETCHED_PTR:
                break;
            case StatmentAttributesEnum.SQL_ATTR_ROW_ARRAY_SIZE:
                break;
            case StatmentAttributesEnum.SQL_ATTR_APP_ROW_DESC:
                break;
            case StatmentAttributesEnum.SQL_ATTR_APP_PARAM_DESC:
                break;
            case StatmentAttributesEnum.SQL_ATTR_IMP_ROW_DESC:
                break;
            case StatmentAttributesEnum.SQL_ATTR_IMP_PARAM_DESC:
                break;
            default:
                Logger.WriteLog(Logger.LogType.Debug, nameof(SQLSetStmtAttrW), $"(Error){nameof(StatementHandle)}:{StatementHandle},{nameof(Attribute)}:{Attribute},{nameof(ValuePtr)}:{ValuePtr},{nameof(StringLength)}:{StringLength})");
                return SQLRETURN.SQL_ERROR;
        }
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLSetStmtAttrW), $"{nameof(StatementHandle)}:{StatementHandle},{nameof(Attribute)}:{Attribute},{nameof(ValuePtr)}:{ValuePtr},{nameof(StringLength)}:{StringLength})");
        return SQLRETURN.SQL_SUCCESS;
    }

SQLGetStmtAttrW

    /// <summary>
    /// ステートメント属性の現在の設定を返します
    /// </summary>
    /// <param name="StatementHandle">[入力] ステートメント ハンドル</param>
    /// <param name="Attribute">[入力] 取得する属性</param>
    /// <param name="ValuePtr">[出力] Attribute で指定された属性の値を返すバッファーへのポインター</param>
    /// <param name="BufferLength">[入力] Attribute が ODBC で定義された属性で 、ValuePtr が文字列またはバイナリ バッファーを指している場合、この引数は *ValuePtr の長さである必要があります</param>
    /// <param name="StringLengthPtr">[出力] *ValuePtr で返すことができる合計バイト数 (null 終端文字を除く) を返すバッファーへのポインター</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLGetStmtAttr(SQLHSTMT StatementHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER BufferLength, SQLINTEGER* StringLengthPtr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlgetstmtattr-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLGetStmtAttrW))]
    public static SQLRETURN SQLGetStmtAttrW(IntPtr StatementHandle, StatmentAttributesEnum Attribute, IntPtr ValuePtr, int BufferLength, IntPtr StringLengthPtr)
    {
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLGetStmtAttrW), $"{nameof(StatementHandle)}:{StatementHandle},{nameof(Attribute)}:{Attribute},{nameof(ValuePtr)}:{ValuePtr},{nameof(BufferLength)}:{BufferLength},{nameof(StringLengthPtr)}:{StringLengthPtr}");
        switch (Attribute)
        {
            case StatmentAttributesEnum.SQL_ATTR_CURSOR_TYPE:
                Marshal.WriteInt32(ValuePtr, (int)CursorTypeEnum.SQL_CURSOR_FORWARD_ONLY);
                break;
            case StatmentAttributesEnum.SQL_ATTR_APP_ROW_DESC:
            case StatmentAttributesEnum.SQL_ATTR_APP_PARAM_DESC:
            case StatmentAttributesEnum.SQL_ATTR_IMP_ROW_DESC:
            case StatmentAttributesEnum.SQL_ATTR_IMP_PARAM_DESC:
                unchecked
                {
                    Marshal.WriteInt32(ValuePtr, (int)DEAD_MAGIC);
                }
                break;
            default:
                break;
        }
        return SQLRETURN.SQL_SUCCESS;
    }

SQLSetDescFieldW

SQL_SUCCESS を返すだけ

    /// <summary>
    /// 記述子レコードの 1 つのフィールドの値を設定します
    /// </summary>
    /// <param name="DescriptorHandle">[入力] 記述子ハンドル</param>
    /// <param name="RecNumber">[入力] アプリケーションが設定を求めるフィールドを含む記述子レコードを示します</param>
    /// <param name="FieldIdentifier">[入力] 値を設定する記述子のフィールドを示します</param>
    /// <param name="ValuePtr">[入力] 記述子情報または整数値を含むバッファーへのポインター</param>
    /// <param name="BufferLength">[入力] FieldIdentifier が ODBC で定義されたフィールドで 、ValuePtr が文字列またはバイナリ バッファーを指している場合、この引数は *ValuePtr の長さである必要があります</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLSetDescField(SQLHDESC DescriptorHandle, SQLSMALLINT RecNumber, SQLSMALLINT FieldIdentifier, SQLPOINTER ValuePtr, SQLINTEGER BufferLength);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlsetdescfield-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLSetDescFieldW))]
    public static SQLRETURN SQLSetDescFieldW(IntPtr DescriptorHandle, short RecNumber, short FieldIdentifier, IntPtr ValuePtr, int BufferLength)
    {
        var value = Marshal.PtrToStringUni(ValuePtr);
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLSetDescFieldW), $"{nameof(DescriptorHandle)}:{DescriptorHandle},{nameof(RecNumber)}:{RecNumber},{nameof(FieldIdentifier)}:{FieldIdentifier},{nameof(value)}:{value},{nameof(BufferLength)}:{BufferLength}");
        return SQLRETURN.SQL_SUCCESS;
    }

SQLFreeStmt

SQL_SUCCESS を返すだけ

    /// <summary>
    /// 特定のステートメントに関連付けられている処理を停止し、ステートメントに関連付けられている開いているカーソルを閉じるか、保留中の結果を破棄するか、または必要に応じて、ステートメント ハンドルに関連付けられているすべてのリソースを解放します
    /// </summary>
    /// <param name="StatementHandle">[入力] ステートメント ハンドル</param>
    /// <param name="Option">[入力] 次のいずれかのオプションがあります(SQL_CLOSE, SQL_DROP, SQL_UNBIND, SQL_RESET_PARAMS)</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLFreeStmt(SQLHSTMT StatementHandle, SQLUSMALLINT Option);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlfreestmt-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLFreeStmt))]
    public static SQLRETURN SQLFreeStmt(IntPtr StatementHandle, FreeStmtEnum Option)
    {
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLFreeStmt), $"{nameof(StatementHandle)}:{StatementHandle},{nameof(Option)}:{Option}");
        return SQLRETURN.SQL_SUCCESS; 
    }

SQLPrepareW

SQL_SUCCESS を返すだけ

    /// <summary>
    /// 実行用に SQL 文字列を準備します
    /// </summary>
    /// <param name="StatementHandle">[入力] ステートメント ハンドル</param>
    /// <param name="StatementText">[入力] SQL テキスト文字列</param>
    /// <param name="TextLength">[入力] *StatementText の文字数</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_STILL_EXECUTING、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLPrepare(SQLHSTMT StatementHandle, SQLCHAR * StatementText, SQLINTEGER TextLength);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlprepare-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLPrepareW))]
    public static SQLRETURN SQLPrepareW(IntPtr StatementHandle, IntPtr StatementText, int TextLength)
    {
        var statementText = Marshal.PtrToStringUni(StatementText);
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLPrepareW), $"{nameof(StatementHandle)}:{StatementHandle},{nameof(statementText)}:{statementText}({TextLength})");
        return SQLRETURN.SQL_SUCCESS;
    }

SQLNumParams

SQL_SUCCESS を返すだけ

    /// <summary>
    /// SQL ステートメント内のパラメーターの数を返します
    /// </summary>
    /// <param name="StatementHandle">[入力] ステートメント ハンドル</param>
    /// <param name="ParameterCountPtr">[出力] ステートメント内のパラメーターの数を返すバッファーへのポインター</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_STILL_EXECUTING、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLNumParams(SQLHSTMT StatementHandle, SQLSMALLINT * ParameterCountPtr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlnumparams-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLNumParams))]
    public static SQLRETURN SQLNumParams(IntPtr StatementHandle, IntPtr ParameterCountPtr)
    {
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLNumParams), $"{nameof(StatementHandle)}:{StatementHandle},{nameof(ParameterCountPtr)}:{ParameterCountPtr}");
        return SQLRETURN.SQL_SUCCESS;
    }

SQLExecDirectW

SQL を実行します。
今回は、結果を格納しておくResultset に対して、列は「Col1」固定で、レコード数は1 レコードで、Col1 カラムの値に入力されたSQL をセットしています。

    /// <summary>
    /// ステートメントにパラメーターが存在する場合は、パラメーター マーカー変数の現在の値を使用して、準備可能なステートメントを実行します
    /// </summary>
    /// <param name="StatementHandle">[入力] ステートメント ハンドル</param>
    /// <param name="StatementText">[入力] 実行する SQL ステートメント</param>
    /// <param name="TextLength">[入力] *StatementText の文字数</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_NEED_DATA、SQL_STILL_EXECUTING、SQL_ERROR、SQL_NO_DATA、SQL_INVALID_HANDLE、またはSQL_PARAM_DATA_AVAILABLE</returns>
    /// <example>SQLRETURN SQLExecDirect(SQLHSTMT StatementHandle, SQLCHAR * StatementText, SQLINTEGER TextLength);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlexecdirect-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLExecDirectW))]
    public static SQLRETURN SQLExecDirectW(IntPtr StatementHandle, IntPtr StatementText, int TextLength)
    {
        var statementText = Marshal.PtrToStringUni(StatementText) ?? string.Empty;
        if (!Resultset.TryGetValue(StatementHandle, out var value))
        {
            value = new Dataset();
            Resultset.Add(StatementHandle, value);
        }
        value.Cols.Add(1, new ColItem() { Name = "Col1", DataTypeCode = SQLTypeEnum.SQL_WVARCHAR, ColumnSize = 255, Nullable = NullableEnum.SQL_NULLABLE });
        value.Rows.Add(1, new Dictionary<int, object> { { 1, statementText } });
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLExecDirectW), $"{nameof(StatementHandle)}:{StatementHandle},{nameof(statementText)}:{statementText}({TextLength})");
        return SQLRETURN.SQL_SUCCESS;
    }

SQLRowCount

この関数は、現在ではあてにしてはいけないらしく、SQLite でも0 を返しているだけだった。

    /// <summary>
    /// UPDATE、INSERT、または DELETE ステートメント、SQLBulkOperations; のSQL_ADD、SQL_UPDATE_BY_BOOKMARK、またはSQL_DELETE_BY_BOOKMARK操作、またはSQLSetPos でのSQL_UPDATEまたはSQL_DELETE操作によって影響を受ける行数を返します
    /// </summary>
    /// <param name="StatementHandle">[入力] ステートメント ハンドル</param>
    /// <param name="RowCountPtr">[出力]行数を返すバッファーを指します</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLRowCount(SQLHSTMT StatementHandle, SQLLEN * RowCountPtr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlrowcount-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLRowCount))]
    public static SQLRETURN SQLRowCount(IntPtr StatementHandle, IntPtr RowCountPtr)
    {
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLRowCount), $"{nameof(StatementHandle)}:{StatementHandle},{nameof(RowCountPtr)}:{RowCountPtr}");
        Marshal.WriteInt64(RowCountPtr, 0);
        return SQLRETURN.SQL_SUCCESS;
    }

SQLNumResultCols

Resultset の行数を返しています。

    /// <summary>
    /// 結果セット内の列数を返します
    /// </summary>
    /// <param name="StatementHandle">[入力] ステートメント ハンドル</param>
    /// <param name="ColumnCountPtr">[出力] 結果セット内の列数を返すバッファーへのポインター</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_STILL_EXECUTING、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLNumResultCols(SQLHSTMT StatementHandle, SQLSMALLINT * ColumnCountPtr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlnumresultcols-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLNumResultCols))]
    public static SQLRETURN SQLNumResultCols(IntPtr StatementHandle, IntPtr ColumnCountPtr)
    {
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLNumResultCols), $"{nameof(StatementHandle)}:{StatementHandle},{nameof(ColumnCountPtr)}:{ColumnCountPtr}");
        Marshal.WriteInt16(ColumnCountPtr, (short)Resultset[StatementHandle].Cols.Count);
        return SQLRETURN.SQL_SUCCESS;
    }

SQLDescribeColW

    /// <summary>
    /// 結果セット内の 1 つの列に対して、結果記述子 (列名、型、列サイズ、10 進数、null 許容) を返します
    /// </summary>
    /// <param name="StatementHandle">[入力] ステートメント ハンドル</param>
    /// <param name="ColumnNumber">[入力]1 から始まる列の順序で順番に並べ替えられた結果データの列番号。 引数 ColumnNumber を 0 に設定して、ブックマーク列を記述することもできます。</param>
    /// <param name="ColumnName">[出力] 列名を返す null で終わるバッファーへのポインター。 この値は、IRD の SQL_DESC_NAME フィールドから読み取られます。 列に名前が付いていない場合、または列名を特定できない場合、ドライバーは空の文字列を返します。
    /// ColumnName が NULL の場合でも、NameLengthPtr は、ColumnName が指すバッファーで返すために使用できる文字数(文字データの null 終端文字を除く) を返します。</param>
    /// <param name="BufferLength">[入力]* ColumnName バッファーの長さ(文字数)。</param>
    /// <param name="NameLengthPtr">[出力]* ColumnName で返すために使用できる文字数(null 終了を除く) を返すバッファーへのポインター。 返される文字数が BufferLength 以上の場合、*ColumnName の列名は BufferLength から null 終端文字の長さを引いた値に切り捨てられます。</param>
    /// <param name="DataTypePtr">[出力] 列の SQL データ型を返すバッファーへのポインター。 この値は、IRD の SQL_DESC_CONCISE_TYPE フィールドから読み取られます。 これは、 SQL データ型の値のいずれか、またはドライバー固有の SQL データ型になります。 データ型を特定できない場合、ドライバーはSQL_UNKNOWN_TYPEを返します。</param>
    /// <param name="ColumnSizePtr">[出力] データ ソースの列のサイズ(文字数) を返すバッファーへのポインター。 列のサイズを決定できない場合、ドライバーは 0 を返します。 列サイズの詳細については、「付録 D: データ型」の 「列サイズ、10 進数、転送オクテット長、および表示サイズ 」を参照してください。</param>
    /// <param name="DecimalDigitsPtr">[出力] データ ソースの列の 10 進数を返すバッファーへのポインター。 10 進数の桁数を特定できない場合、または該当しない場合、ドライバーは 0 を返します。 10 進数の詳細については、「付録 D: データ型」の 「列のサイズ、10 進数、転送オクテットの長さ、および表示サイズ 」を参照してください。</param>
    /// <param name="NullablePtr">[出力] 列で NULL 値が許可されるかどうかを示す値を返すバッファーへのポインター (SQL_NO_NULLS=列は NULL 値を許可しません, SQL_NULLABLE=列はNULL値を許可します, SQL_NULLABLE_UNKNOWN=ドライバーは、列でNULL値が許可されているかどうかを判断できません)</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_STILL_EXECUTING、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLDescribeCol(SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLCHAR * ColumnName, SQLSMALLINT BufferLength, SQLSMALLINT * NameLengthPtr, SQLSMALLINT * DataTypePtr, SQLULEN * ColumnSizePtr, SQLSMALLINT * DecimalDigitsPtr, SQLSMALLINT * NullablePtr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqldescribecol-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLDescribeColW))]
    public static SQLRETURN SQLDescribeColW(IntPtr StatementHandle, ushort ColumnNumber, IntPtr ColumnName, short BufferLength, IntPtr NameLengthPtr, IntPtr DataTypePtr, IntPtr ColumnSizePtr, IntPtr DecimalDigitsPtr, IntPtr NullablePtr)
    {
        var col = Resultset[StatementHandle].Cols[ColumnNumber];
        WriteUniStringToPointer(col.Name, ColumnName, BufferLength, NameLengthPtr);
        Marshal.WriteInt16(DataTypePtr, (short)col.DataTypeCode);
        Marshal.WriteInt64(ColumnSizePtr, col.ColumnSize);
        Marshal.WriteInt16(DecimalDigitsPtr, (short)col.DecimalDigits);
        Marshal.WriteInt16(NullablePtr, (short)col.Nullable);
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLDescribeColW), $"{nameof(StatementHandle)}:{StatementHandle},{nameof(ColumnNumber)}:{ColumnNumber},{nameof(ColumnName)}:{ColumnName},{nameof(BufferLength)}:{BufferLength},{nameof(NameLengthPtr)}:{NameLengthPtr},{nameof(DataTypePtr)}:{DataTypePtr},{nameof(ColumnSizePtr)}:{ColumnSizePtr},{nameof(DecimalDigitsPtr)}:{DecimalDigitsPtr},{nameof(NullablePtr)}:{NullablePtr}");
        return SQLRETURN.SQL_SUCCESS;
    }

SQLColAttributeW

SQLColAttributesW に相当します。

    /// <summary>
    /// 結果セット内の列の記述子情報を返します
    /// </summary>
    /// <param name="StatementHandle">[入力] ステートメント ハンドル</param>
    /// <param name="ColumnNumber">[入力] フィールド値の取得元となる IRD 内のレコードの番号</param>
    /// <param name="FieldIdentifier">[入力] 記述子ハンドル。 このハンドルは、IRD のどのフィールドにクエリを実行するかを定義します (たとえば、SQL_COLUMN_TABLE_NAME)</param>
    /// <param name="CharacterAttributePtr">[出力] フィールドが文字列の場合、IRD の ColumnNumber 行の FieldIdentifier フィールドの値を返すバッファーへのポインター</param>
    /// <param name="BufferLength">[入力] FieldIdentifier が ODBC で定義されたフィールドで、 CharacterAttributePtr が文字列またはバイナリ バッファーを指している場合、この引数は *CharacterAttributePtr の長さである必要があります</param>
    /// <param name="StringLengthPtr">[出力] *CharacterAttributePtr で返すことができる合計バイト数 (文字データの null 終了バイトを除く) を返すバッファーへのポインター</param>
    /// <param name="NumericAttributePtr">[出力] フィールドが SQL_DESC_COLUMN_LENGTH などの数値記述子型の場合、IRD の ColumnNumber 行の FieldIdentifier フィールドの値を返す整数バッファーへのポインター</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_STILL_EXECUTING、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLColAttribute (SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLUSMALLINT FieldIdentifier, SQLPOINTER CharacterAttributePtr, SQLSMALLINT BufferLength, SQLSMALLINT * StringLengthPtr, SQLLEN * NumericAttributePtr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlcolattribute-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLColAttributeW))]
    public static SQLRETURN SQLColAttributeW(IntPtr StatementHandle, ushort ColumnNumber, FieldIdentifierEnum FieldIdentifier, IntPtr CharacterAttributePtr, short BufferLength, IntPtr StringLengthPtr, IntPtr NumericAttributePtr)
    {
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLColAttributeW), $"{nameof(StatementHandle)}:{StatementHandle},{nameof(ColumnNumber)}:{ColumnNumber},{nameof(FieldIdentifier)}:{FieldIdentifier},{nameof(CharacterAttributePtr)}:{CharacterAttributePtr},{nameof(BufferLength)}:{BufferLength},{nameof(StringLengthPtr)}:{StringLengthPtr},{nameof(NumericAttributePtr)}:{NumericAttributePtr}");
        var col = Resultset[StatementHandle].Cols[ColumnNumber];
        switch (FieldIdentifier)
        {
            case FieldIdentifierEnum.SQL_COLUMN_COUNT:
                Marshal.WriteInt16(NumericAttributePtr, 1);
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_NAME:
                WriteUniStringToPointer("Col0", CharacterAttributePtr, BufferLength, StringLengthPtr);
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_TYPE:
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_LENGTH:
                Marshal.WriteInt16(NumericAttributePtr, 255);
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_PRECISION:
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_SCALE:
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_DISPLAY_SIZE:
                Marshal.WriteInt16(NumericAttributePtr, 255);
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_NULLABLE:
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_UNSIGNED:
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_MONEY:
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_UPDATABLE:
                Marshal.WriteInt16(NumericAttributePtr, (short)(col.IsUpdatable ? ColUpdatableEnum.SQL_ATTR_WRITE : ColUpdatableEnum.SQL_ATTR_READONLY));
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_AUTO_INCREMENT:
                Marshal.WriteInt16(NumericAttributePtr, (short)SQL_FALSE);
                return SQLRETURN.SQL_SUCCESS;
            //case FieldIdentifierEnum.SQL_COLUMN_CASE_SENSITIVE:
            //    break;
            //case FieldIdentifierEnum.SQL_COLUMN_SEARCHABLE:
            //    break;
            //case FieldIdentifierEnum.SQL_COLUMN_TYPE_NAME:
            //    break;
            case FieldIdentifierEnum.SQL_COLUMN_TABLE_NAME:
                WriteUniStringToPointer("Dummy0", CharacterAttributePtr, BufferLength, StringLengthPtr);
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_OWNER_NAME:
                WriteUniStringToPointer("Dummy1", CharacterAttributePtr, BufferLength, StringLengthPtr);
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_QUALIFIER_NAME:
                WriteUniStringToPointer("Dummy2", CharacterAttributePtr, BufferLength, StringLengthPtr);
                return SQLRETURN.SQL_SUCCESS;
            case FieldIdentifierEnum.SQL_COLUMN_LABEL:
                WriteUniStringToPointer(col.Name, CharacterAttributePtr, BufferLength, StringLengthPtr);
                return SQLRETURN.SQL_SUCCESS;
            default:
                break;
        }
        Logger.WriteLog(Logger.LogType.Error, nameof(SQLColAttributeW), $"{nameof(StatementHandle)}:{StatementHandle},{nameof(ColumnNumber)}:{ColumnNumber},{nameof(FieldIdentifier)}:{FieldIdentifier},{nameof(CharacterAttributePtr)}:{CharacterAttributePtr},{nameof(BufferLength)}:{BufferLength},{nameof(StringLengthPtr)}:{StringLengthPtr},{nameof(NumericAttributePtr)}:{NumericAttributePtr}");
        return SQLRETURN.SQL_ERROR;
    }

SQLNativeSqlW

今回のケースでは、引数のSQL をそのまま返すだけです。

    /// <summary>
    /// ドライバーによって変更された SQL 文字列を返します
    /// </summary>
    /// <param name="ConnectionHandle">[入力] 接続ハンドル</param>
    /// <param name="InStatementText">[入力] 変換する SQL テキスト文字列</param>
    /// <param name="TextLength1">[入力] *InStatementText テキスト文字列の文字数</param>
    /// <param name="OutStatementText">[出力] 変換された SQL 文字列を返すバッファーへのポインター</param>
    /// <param name="BufferLength">[入力] *OutStatementText バッファー内の文字数</param>
    /// <param name="TextLength2Ptr">[出力] *OutStatementText で返すために使用できる (null 終端を除く) 文字数の合計を返すバッファーへのポインター</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLNativeSql(SQLHDBC ConnectionHandle, SQLCHAR * InStatementText, SQLINTEGER TextLength1, SQLCHAR * OutStatementText, SQLINTEGER BufferLength, SQLINTEGER * TextLength2Ptr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlnativesql-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLNativeSqlW))]
    public static SQLRETURN SQLNativeSqlW(IntPtr ConnectionHandle, IntPtr InStatementText, int TextLength1, IntPtr OutStatementText, int BufferLength, IntPtr TextLength2Ptr)
    {
        var inStatementText = Marshal.PtrToStringUni(InStatementText) ?? string.Empty;
        WriteUniStringToPointer(inStatementText, OutStatementText, (short)BufferLength, TextLength2Ptr);
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLNativeSqlW), $"{nameof(ConnectionHandle)}:{ConnectionHandle},{nameof(inStatementText)}:{inStatementText}({TextLength1}),{nameof(OutStatementText)};{OutStatementText},{nameof(BufferLength)}:{BufferLength},{nameof(TextLength2Ptr)}:{TextLength2Ptr}");
        return SQLRETURN.SQL_INVALID_HANDLE;
    }

SQLBindCol

Fetch 関数で列の値を書き込むポインタを登録します。

    /// <summary>
    /// 結果セット内の列にアプリケーション データ バッファーをバインドします
    /// </summary>
    /// <param name="StatementHandle">[入力]ステートメント ハンドル</param>
    /// <param name="ColumnNumber">[入力]バインドする結果セット列の番号</param>
    /// <param name="TargetType">[入力]*TargetValuePtr バッファーの C データ型の識別子</param>
    /// <param name="TargetValuePtr">[遅延入出力]列にバインドするデータ バッファーへのポインター</param>
    /// <param name="BufferLength">[入力]*TargetValuePtr バッファーの長さ (バイト単位)</param>
    /// <param name="StrLen_or_IndPtr">[遅延入出力]列にバインドする長さ/インジケーター バッファーへのポインター</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLBindCol(SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, SQLPOINTER TargetValuePtr, SQLLEN BufferLength, SQLLEN * StrLen_or_IndPtr);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlbindcol-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLBindCol))]
    public static SQLRETURN SQLBindCol(IntPtr StatementHandle, ushort ColumnNumber, SQLTypeEnum TargetType, IntPtr targetValuePtr, int BufferLength, IntPtr strLen_or_IndPtr)
    {
        if (!Resultset.TryGetValue(StatementHandle, out var dataset))
        {
            Logger.WriteLog(Logger.LogType.Debug, nameof(SQLBindCol), $"{nameof(StatementHandle)}:{StatementHandle} not found.");
            return SQLRETURN.SQL_ERROR;
        }
        if (!dataset.Cols.TryGetValue(ColumnNumber, out var colItem))
        {
            colItem = new ColItem();
            dataset.Cols.Add(ColumnNumber, colItem);
        }
        colItem.TargetType = TargetType;
        colItem.TargetValuePtr = targetValuePtr;
        colItem.BufferLength = BufferLength;
        colItem.StrLen_or_IndPtr = strLen_or_IndPtr;
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLBindCol), $"{nameof(StatementHandle)}:{StatementHandle},{nameof(ColumnNumber)}:{ColumnNumber},{nameof(TargetType)}:{TargetType},{nameof(targetValuePtr)}:{targetValuePtr},{nameof(BufferLength)}:{BufferLength},{nameof(strLen_or_IndPtr)}:{strLen_or_IndPtr}");
        return SQLRETURN.SQL_SUCCESS;
    }

SQLExtendedFetch

    /// <summary>
    /// 結果セットから指定されたデータ行セットをフェッチし、バインドされているすべての列のデータを返します
    /// </summary>
    /// <param name="StatementHandle">[入力] ステートメント ハンドル</param>
    /// <param name="FetchOrientation">[入力] フェッチの種類</param>
    /// <param name="FetchOffset">[入力] フェッチする行の番号</param>
    /// <param name="RowCountPtr">[出力] 実際にフェッチされた行数を返すバッファーへのポインター</param>
    /// <param name="RowStatusArray">[出力] 各行の状態を返す配列へのポインター</param>
    /// <returns>SQL_SUCCESS、SQL_SUCCESS_WITH_INFO、SQL_NO_DATA、SQL_STILL_EXECUTING、SQL_ERROR、またはSQL_INVALID_HANDLE</returns>
    /// <example>SQLRETURN SQLExtendedFetch(SQLHSTMT StatementHandle, SQLUSMALLINT FetchOrientation, SQLLEN FetchOffset, SQLULEN * RowCountPtr, SQLUSMALLINT * RowStatusArray);</example>
    /// <remarks>https://learn.microsoft.com/ja-jp/sql/odbc/reference/syntax/sqlextendedfetch-function</remarks>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)], EntryPoint = nameof(SQLExtendedFetch))]
    public static SQLRETURN SQLExtendedFetch(IntPtr StatementHandle, ushort FetchOrientation, int FetchOffset, IntPtr RowCountPtr, IntPtr RowStatusArray)
    {
        Logger.WriteLog(Logger.LogType.Debug, nameof(SQLExtendedFetch), $"{nameof(StatementHandle)}:{StatementHandle},{nameof(FetchOrientation)}:{FetchOrientation},{nameof(FetchOffset)}:{FetchOffset},{nameof(RowCountPtr)}:{RowCountPtr},{nameof(RowStatusArray)}:{RowStatusArray}");
        if (!Resultset.TryGetValue(StatementHandle, out var dataset)) return SQLRETURN.SQL_NO_DATA;
        dataset.MoveNext();
        var result = dataset.Fetch();
        if (!result) return SQLRETURN.SQL_NO_DATA;
        Marshal.WriteInt32(RowCountPtr, 1);
        Marshal.WriteInt16(RowStatusArray, (short)RowStatusEnum.SQL_ROW_SUCCESS);
        return SQLRETURN.SQL_SUCCESS;
    }

デバッグの仕方

今回の目標は、A5 SQL:Mk-2 でSQL を実行して結果を得ることなので、ひたすらSQL を実行してはログファイルを眺めながら躓いたポイントを潰していく作業になります。

そして、最終的に出来上がったものが、最初のスクリーンショットです。

まとめ

単に固定値を返すだけの「Hello, World!」レベルなら難しくない、そう思っていた時期が私にもありました・・・

最終的にSQLite のドライバーが教科書になってくれたおかげで、乗り越えることができましたが、いざ作ろうと思ってみたら、具体的な作り方がネット上にほぼなく、リファレンス資料としては、Micorosoft のページはあるものの、個々の関数などの仕様はあるものの、関数同士の関係性など全体的な仕組みが分からず苦労しました。

今回、お手本ありではありますが、スクラッチでODBC ドライバーがかけたので、ODBC チョットワカルぐらいのレベルにはなれたのではないかと思います。

普段、C# を愛用していますが、ここまでポインターだらけのプログラムを書くのは初めてだったので、C# のプログラムとしても、かなりいい経験になりました。

  1. サルベド猫で検索

  2. Open Database Connectivity

  3. Native AOT deployment

  4. VB6(VBA)からC#製DLLを呼ぶ際のマーシャリング術

  5. GitHub - softace/sqliteodbc: SQLite ODBC driver

  6. ODBC API リファレンス - ODBC API Reference | Microsoft Learn

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?