Quickstart: Terhubung dan melakukan query dengan C++ dan ODBC

Berlaku untuk: SQL ServerAzure SQL Database

Dalam quickstart ini, Anda membangun aplikasi konsol C++ di Windows, Linux, atau macOS. Aplikasi terhubung ke AdventureWorksLT database dengan menggunakan Microsoft ODBC Driver 18 untuk SQL Server, mengikat parameter query, menjalankan query, dan membaca baris hasilnya.

Prerequisites

  • Microsoft ODBC Driver 18 untuk SQL Server. Instal driver untuk Windows, Linux, atau macOS.
  • Sebuah kompiler C++17 dan berkas pengembangan ODBC platform:
    • Di Windows, instal Visual Studio 2022 atau Build Tools untuk Visual Studio 2022 dengan beban kerja pengembangan Desktop dengan C++. Windows SDK menyediakan header ODBC dan odbc32.lib.
    • Di Linux, instal compiler C++ dan paket pengembangan unixODBC untuk distribusi Anda. Paket menyediakan header ODBC dan libodbc.
    • Di macOS, instal alat baris perintah Xcode dan unixODBC dari Homebrew.
  • Sebuah database di Azure SQL Database atau SQL Server yang berisi AdventureWorksLT data contoh. Untuk Azure SQL Database, pilih sumber data Contoh saat Anda membuat satu basis data. Untuk SQL Server, pulihkan file cadangan AdventureWorksLT dari database sampel AdventureWorks.

Verifikasi driver tersebut

Pastikan driver manager dapat menemukan Microsoft ODBC Driver 18 untuk SQL Server.

Jalankan perintah ini di PowerShell:

Get-OdbcDriver -Name "ODBC Driver 18 for SQL Server"

Setiap perintah harus mencantumkan ODBC Driver 18 for SQL Server. Jika driver tidak tercantum, instal ulang sebelum melanjutkan.

Konfigurasikan koneksi

Aplikasi membaca seluruh string koneksi dari variabel lingkungan ODBC_CONNECTION_STRING. Aplikasi ini tidak menampilkan string koneksi atau menerimanya sebagai argumen baris perintah.

Gunakan string koneksi Driver 18 yang valid untuk database dan metode autentikasi Anda. Contoh autentikasi SQL berikut bekerja di Windows, Linux, dan macOS ketika database mengizinkan autentikasi SQL:

Driver={ODBC Driver 18 for SQL Server};Server=tcp:<server>,1433;Database=<database>;UID=<user_id>;PWD=<password>;Encrypt=yes;TrustServerCertificate=no;

Untuk opsi otentikasi Microsoft Entra, lihat Gunakan Microsoft Entra ID dengan driver ODBC. Untuk semua pengaturan yang didukung, lihat kata kunci dan atribut DSN serta string koneksi.

Microsoft ODBC Driver 18 mengaktifkan enkripsi secara default. Tentukan Encrypt=yes agar persyaratan aplikasi secara eksplisit. Biarkan TrustServerCertificate=no dalam mode produksi agar driver memvalidasi sertifikat server. Sertifikat harus mencocokkan nama server dan rantai dengan otoritas sertifikasi yang dipercaya oleh klien. Untuk panduan konfigurasi, lihat Kegagalan validasi sertifikat.

Caution

TrustServerCertificate=yes melewatkan validasi sertifikat. Gunakan hanya untuk pengembangan lokal terisolasi saat Anda mengonfigurasi sertifikat yang dipercaya klien. Jangan gunakan dalam produksi.

Atur variabel lingkungan tanpa menambahkan string koneksi ke riwayat shell Anda.

Buka Developer PowerShell untuk VS 2022. Jalankan perintah-perintah ini, lalu tempelkan string koneksi di prompt:

$secureConnectionString = Read-Host "ODBC connection string" -AsSecureString
$env:ODBC_CONNECTION_STRING = [System.Net.NetworkCredential]::new(
    "", $secureConnectionString).Password
$secureConnectionString = $null

Variabel lingkungan menyimpan string koneksi di luar file sumber, tetapi proses tersebut dan proses turunannya dapat membacanya. Untuk aplikasi produksi, gunakan autentikasi Microsoft Entra jika memungkinkan dan ambil data rahasia dari penyimpanan aman saat aplikasi berjalan.

Buat aplikasi

  1. Buat direktori proyek dan ubah ke direktori tersebut:

    New-Item -ItemType Directory odbc-quickstart
    Set-Location odbc-quickstart
    

  1. Buat file bernama odbc-quickstart.cpp dengan kode berikut:

    #ifdef _WIN32
    #include <windows.h>
    #endif
    
    #include <sql.h>
    #include <sqlext.h>
    #include <sqltypes.h>
    
    #include <cstdlib>
    #include <iomanip>
    #include <iostream>
    #include <string>
    #include <string_view>
    
    std::string ReadEnvironmentVariable(const char* name)
    {
    #ifdef _WIN32
        char* value = nullptr;
        std::size_t length = 0;
        if (_dupenv_s(&value, &length, name) != 0 || value == nullptr)
            return {};
    
        std::string result(value);
        std::free(value);
        return result;
    #else
        const char* value = std::getenv(name);
        return value == nullptr ? std::string{} : value;
    #endif
    }
    
    bool PrintDiagnostics(SQLSMALLINT handleType, SQLHANDLE handle)
    {
        bool dataTruncated = false;
        SQLCHAR state[6];
        SQLINTEGER nativeError;
        SQLCHAR message[SQL_MAX_MESSAGE_LENGTH];
        SQLSMALLINT messageLength;
    
        for (SQLSMALLINT record = 1;
             SQL_SUCCEEDED(SQLGetDiagRec(handleType, handle, record, state,
                                         &nativeError, message, sizeof(message),
                                         &messageLength));
             ++record)
        {
            std::cerr << '[' << state << "] (" << nativeError << ") "
                      << message << '\n';
            if (std::string_view(reinterpret_cast<const char*>(state), 5) == "01004")
                dataTruncated = true;
        }
        return dataTruncated;
    }
    
    bool Succeeded(SQLRETURN result, SQLSMALLINT handleType, SQLHANDLE handle,
                   bool* dataTruncated = nullptr)
    {
        if (result == SQL_SUCCESS)
            return true;
    
        const bool diagnosticReportsTruncation = PrintDiagnostics(handleType, handle);
        if (dataTruncated != nullptr)
            *dataTruncated = diagnosticReportsTruncation;
        return result == SQL_SUCCESS_WITH_INFO;
    }
    
    struct OdbcHandles
    {
        SQLHENV environment = SQL_NULL_HENV;
        SQLHDBC connection = SQL_NULL_HDBC;
        SQLHSTMT statement = SQL_NULL_HSTMT;
    
        ~OdbcHandles()
        {
            if (statement != SQL_NULL_HSTMT)
                SQLFreeHandle(SQL_HANDLE_STMT, statement);
            if (connection != SQL_NULL_HDBC)
            {
                SQLDisconnect(connection);
                SQLFreeHandle(SQL_HANDLE_DBC, connection);
            }
            if (environment != SQL_NULL_HENV)
                SQLFreeHandle(SQL_HANDLE_ENV, environment);
        }
    };
    
    int main()
    {
        std::string connectionString =
            ReadEnvironmentVariable("ODBC_CONNECTION_STRING");
        if (connectionString.empty())
        {
            std::cerr << "Set ODBC_CONNECTION_STRING before running.\n";
            return 1;
        }
    
        OdbcHandles handles;
        SQLRETURN result = SQLAllocHandle(
            SQL_HANDLE_ENV, SQL_NULL_HANDLE, &handles.environment);
        if (!Succeeded(result, SQL_HANDLE_ENV, handles.environment))
        {
            std::cerr << "Unable to allocate an ODBC environment handle.\n";
            return 1;
        }
    
        result = SQLSetEnvAttr(
            handles.environment,
            SQL_ATTR_ODBC_VERSION,
            reinterpret_cast<SQLPOINTER>(SQL_OV_ODBC3_80),
            0);
        if (!Succeeded(result, SQL_HANDLE_ENV, handles.environment))
            return 1;
    
        result = SQLAllocHandle(
            SQL_HANDLE_DBC, handles.environment, &handles.connection);
        if (!Succeeded(result, SQL_HANDLE_ENV, handles.environment))
            return 1;
    
        result = SQLDriverConnect(
            handles.connection,
            nullptr,
            reinterpret_cast<SQLCHAR*>(connectionString.data()),
            SQL_NTS,
            nullptr,
            0,
            nullptr,
            SQL_DRIVER_NOPROMPT);
        if (!Succeeded(result, SQL_HANDLE_DBC, handles.connection))
            return 1;
    
        result = SQLAllocHandle(
            SQL_HANDLE_STMT, handles.connection, &handles.statement);
        if (!Succeeded(result, SQL_HANDLE_DBC, handles.connection))
            return 1;
    
        SQLINTEGER minimumProductId = 0;
        SQLLEN minimumProductIdLength = 0;
        result = SQLBindParameter(
            handles.statement,
            1,
            SQL_PARAM_INPUT,
            SQL_C_SLONG,
            SQL_INTEGER,
            10,
            0,
            &minimumProductId,
            0,
            &minimumProductIdLength);
        if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
            return 1;
    
        SQLCHAR query[] =
            "SELECT TOP (5) ProductID, Name "
            "FROM SalesLT.Product "
            "WHERE ProductID > ? "
            "ORDER BY ProductID;";
        result = SQLExecDirect(handles.statement, query, SQL_NTS);
        if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
            return 1;
    
        std::cout << "Product ID  Name\n"
                  << "----------  ----\n";
    
        while ((result = SQLFetch(handles.statement)) != SQL_NO_DATA)
        {
            if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
                return 1;
    
            SQLINTEGER productId = 0;
            SQLLEN productIdLength = 0;
            SQLCHAR productName[256] = {};
            SQLLEN productNameLength = 0;
    
            result = SQLGetData(
                handles.statement, 1, SQL_C_SLONG, &productId,
                sizeof(productId), &productIdLength);
            if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
                return 1;
    
            bool productNameTruncated = false;
            result = SQLGetData(
                handles.statement, 2, SQL_C_CHAR, productName,
                sizeof(productName), &productNameLength);
            if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement,
                           &productNameTruncated))
                return 1;
    
            std::string productNameText;
            if (productNameLength == SQL_NULL_DATA)
            {
                productNameText = "<NULL>";
            }
            else if (productNameTruncated ||
                     productNameLength > static_cast<SQLLEN>(sizeof(productName) - 1))
            {
                std::cerr << "Product name was truncated; no partial value was printed.\n";
                return 1;
            }
            else if (productNameLength == SQL_NO_TOTAL)
            {
                std::cerr << "Product name length is unknown; no value was printed.\n";
                return 1;
            }
            else if (productNameLength < 0)
            {
                std::cerr << "The driver returned an invalid product-name length.\n";
                return 1;
            }
            else if (productNameLength == 0)
            {
                productNameText = "<empty>";
            }
            else
            {
                productNameText.assign(
                    reinterpret_cast<const char*>(productName),
                    static_cast<std::size_t>(productNameLength));
            }
    
            std::cout << std::left << std::setw(12) << productId
                      << productNameText << '\n';
        }
    
        return 0;
    }
    

Aplikasi ini hanya menggunakan API ODBC standar, sehingga menyertakan header driver-manager dan tautan ke pustaka driver-manager. string koneksi memilih Microsoft ODBC Driver 18 untuk SQL Server saat runtime.

SQL_DRIVER_NOPROMPT mencegah SQLDriverConnect membuka dialog konfigurasi. Jika string koneksi tidak lengkap, pemanggilan akan menghasilkan kesalahan dan aplikasi mencetak semua rekaman diagnostik. Aplikasi ini juga mencetak diagnostik saat panggilan ODBC yang diperiksa mengembalikan SQL_SUCCESS_WITH_INFO, yang berarti panggilan berhasil dengan peringatan.

Kueri mengikat 0 sebagai parameter SQL Server int, membaca lima produk pertama dari SalesLT.Product, dan mengambil setiap ID produk serta namanya dengan SQLGetData. Untuk nama produk, aplikasi menampilkan <NULL> untuk SQL_NULL_DATA dan <empty> untuk nilai nol panjang. Jika SQLGetData melaporkan SQLSTATE 01004, panjang yang tidak diketahui, atau panjang yang lebih besar dari buffer, aplikasi melaporkan masalah dan keluar tanpa mencetak nama sebagian.

Membangun dan menjalankan aplikasi

  1. Di jendela Developer PowerShell yang sama, kompilasi aplikasi:

    cl /std:c++17 /EHsc /W4 odbc-quickstart.cpp /link odbc32.lib
    
  2. Jalankan aplikasi:

    .\odbc-quickstart.exe
    

Output standar aplikasi adalah:

Product ID  Name
----------  ----
680         HL Road Frame - Black, 58
706         HL Road Frame - Red, 58
707         Sport-100 Helmet, Red
708         Sport-100 Helmet, Black
709         Mountain Bike Socks, M

Hapus string koneksi setelah selesai.

Remove-Item Env:\ODBC_CONNECTION_STRING