PostgreSQL C++ 드라이버를 활용한 리눅스 데이터베이스 연동

이 문서는 리눅스 환경에서 PostgreSQL 데이터베이스와 C++ 애플리케이션을 연동하는 방법을 안내합니다. 특히 libpqxx 라이브러리를 활용하여 데이터베이스를 설정하고, 기본적인 데이터 조작(CRUD) 및 스키마 관리(DDL) 작업을 수행하는 C++ 예제 코드를 제공합니다.

PostgreSQL 설치

먼저 리눅스 시스템에 PostgreSQL 데이터베이스 서버를 설치합니다. 주로 Debian/Ubuntu 계열에서는 apt 패키지 관리자를 사용합니다.

sudo apt-get update
sudo apt-get install postgresql

설치 완료 후, 다음 명령어로 PostgreSQL 서버 버전을 확인하여 정상적으로 설치되었는지 검증할 수 있습니다.

psql --version

C/C++ 드라이버 설치

C++ 애플리케이션에서 PostgreSQL과 통신하기 위해서는 클라이언트 라이브러리가 필요합니다. 여기서는 C 언어 바인딩인 libpq와 C++ 래퍼인 libpqxx를 설치합니다.

libpq 개발 라이브러리는 다음 명령어로 설치합니다:

sudo apt-get install libpq-dev

libpqxx는 소스 코드에서 컴파일하여 설치할 수 있습니다. 예를 들어, libpqxx-7.7.4.tar.gz 버전을 사용하는 경우 다음 단계를 따릅니다 (버전은 다를 수 있습니다):

wget https://github.com/libpqxx/libpqxx/releases/download/7.7.4/libpqxx-7.7.4.tar.gz
tar -zxvf libpqxx-7.7.4.tar.gz
cd libpqxx-7.7.4/
./configure --disable-documentation
make
sudo make install

--disable-documentation 옵션은 문서 생성을 건너뛰어 컴파일 시간을 단축합니다.

PostgreSQL 연동 C++ 예제

데이터베이스 연동을 위한 C++ 코드는 크게 헤더 파일, 소스 파일, 그리고 메인 실행 파일로 구성됩니다. 여기서는 데이터베이스 연결 및 모든 CRUD/DDL 작업을 관리하는 PgConnector 싱글톤 클래스를 구현합니다.

1. 헤더 파일 (PgConnector.h)

#ifndef PG_CONNECTOR_H
#define PG_CONNECTOR_H

#include <string>
#include <vector>
#include <map>
#include <memory> // For std::unique_ptr
#include <iostream> // For basic output

#include <pqxx/pqxx> // PostgreSQL C++ client library

// 컬럼 정의를 위한 구조체
struct ColumnDefinition {
    std::string name;
    std::string type;
    bool isNotNull = false;
    bool isPrimaryKey = false; // Note: For this example, primary key is 'id' BIGSERIAL.
};

// 데이터베이스 연결 및 작업을 관리하는 싱글톤 클래스
class PgConnector {
public:
    // 싱글톤 인스턴스 접근 메서드
    static PgConnector& getInstance();

    // 스레드별 데이터베이스 연결 설정
    bool establishThreadConnection(const std::string& dbName, const std::string& user, const std::string& password);
    // 스레드별 데이터베이스 연결 해제
    void closeThreadConnection();

    // 데이터 삽입
    bool insertData(const std::string& tableName, const std::map<std::string, std::string>& rowData);
    // 데이터 갱신
    bool updateData(const std::string& tableName, const std::string& conditionCol, const std::string& conditionVal,
                    const std::map<std::string, std::string>& newValues);
    // 데이터 조회
    std::vector<std::vector<std::string>> retrieveData(const std::string& tableName,
                                                       const std::vector<std::string>& columnsToSelect = {},
                                                       const std::string& conditionCol = "",
                                                       const std::string& conditionVal = "");
    // 데이터 삭제
    bool deleteData(const std::string& tableName, const std::string& conditionCol = "", const std::string& conditionVal = "");

    // DDL 작업 - 테이블 추가
    bool addTable(const std::string& tableName, const std::vector<ColumnDefinition>& columns);
    // DDL 작업 - 테이블 삭제
    bool dropTable(const std::string& tableName);

    // DDL 작업 - 컬럼 추가
    bool addColumnToTable(const std::string& tableName, const ColumnDefinition& column);
    // DDL 작업 - 컬럼 삭제
    bool removeColumnFromTable(const std::string& tableName, const std::string& columnName);

private:
    // 싱글톤 패턴을 위한 비공개 생성자 및 복사/할당 방지
    PgConnector();
    PgConnector(const PgConnector&) = delete;
    PgConnector& operator=(const PgConnector&) = delete;
    ~PgConnector();

    // 초기 데이터베이스 설정 (데이터베이스 및 사용자 생성 등)
    static bool setupInitialDatabase();
    // 초기 테이블 생성 (사전 정의된 스키마에 따라)
    static bool createDefinedTables();

    // 데이터베이스/사용자 생성
    bool createDbAndUser(const std::string& dbname, const std::string& user, const std::string& password);

    // 싱글톤 인스턴스
    static PgConnector* s_instance;
    // DDL 작업에 사용될 관리자 연결 (주로 초기 설정에 사용)
    static std::unique_ptr<pqxx::connection> s_adminConnection;
    // 각 스레드별 데이터베이스 연결
    static thread_local std::unique_ptr<pqxx::connection> s_threadConnection;

    // 정의된 테이블 스키마 맵 (테이블 이름 -> 컬럼 정의 벡터)
    static std::map<std::string, std::vector<ColumnDefinition>> s_tableSchemas;

    // 관리자 연결 정보 (s_adminConnection용)
    static std::string s_adminDbName;
    static std::string s_adminUser;
    static std::string s_adminPassword;

    // 스키마 초기화 여부
    static bool s_schemaInitialized;
};

#endif // PG_CONNECTOR_H

2. 소스 파일 (PgConnector.cpp)

#include "PgConnector.h"
#include <sstream> // For std::ostringstream
#include <algorithm> // For std::transform

PgConnector* PgConnector::s_instance = nullptr;
std::unique_ptr<pqxx::connection> PgConnector::s_adminConnection = nullptr;
thread_local std::unique_ptr<pqxx::connection> PgConnector::s_threadConnection = nullptr;
std::map<std::string, std::vector<ColumnDefinition>> PgConnector::s_tableSchemas;
std::string PgConnector::s_adminDbName;
std::string PgConnector::s_adminUser;
std::string PgConnector::s_adminPassword;
bool PgConnector::s_schemaInitialized = false;

PgConnector::PgConnector() {
    // 기본 관리자 연결 정보 설정 (필요시 외부 설정으로 변경 가능)
    s_adminDbName = "postgres"; // 시스템 데이터베이스
    s_adminUser = "postgres";
    s_adminPassword = "postgres"; // 주의: 실제 환경에서는 강력한 암호 사용

    if (!s_schemaInitialized) {
        setupInitialDatabase();
        s_schemaInitialized = true;
    }
}

PgConnector::~PgConnector() {
    // s_adminConnection은 unique_ptr이 관리하므로 명시적 delete 불필요
    // thread_local s_threadConnection도 스레드 종료 시 자동으로 소멸
    std::cout << "PgConnector 인스턴스 소멸됨." << std::endl;
}

PgConnector& PgConnector::getInstance() {
    if (!s_instance) {
        s_instance = new PgConnector();
    }
    return *s_instance;
}

bool PgConnector::createDbAndUser(const std::string& dbname, const std::string& user, const std::string& password) {
    if (!s_adminConnection || !s_adminConnection->is_open()) {
        std::string connStr = "dbname=" + s_adminDbName + " user=" + s_adminUser + " password=" + s_adminPassword +
                              " hostaddr=127.0.0.1 port=5432";
        try {
            s_adminConnection.reset(new pqxx::connection(connStr));
            if (!s_adminConnection->is_open()) {
                std::cerr << "관리자 연결 실패: " << connStr << std::endl;
                return false;
            }
        } catch (const pqxx::broken_connection& e) {
            std::cerr << "관리자 연결 예외 (pqxx::broken_connection): " << e.what() << std::endl;
            return false;
        } catch (const std::exception& e) {
            std::cerr << "관리자 연결 예외: " << e.what() << std::endl;
            return false;
        }
    }

    try {
        pqxx::nontransaction txn(*s_adminConnection);

        // 사용자 존재 여부 확인 및 생성
        std::string checkUserSql = "SELECT 1 FROM pg_user WHERE usename = " + txn.quote(user);
        if (txn.exec(checkUserSql).empty()) {
            std::string createUserSql = "CREATE USER " + txn.quote_identifier(user) + " WITH PASSWORD " + txn.quote(password);
            txn.exec(createUserSql);
            std::cout << "사용자 '" << user << "' 생성 완료." << std::endl;
        } else {
            std::cout << "사용자 '" << user << "' 이미 존재." << std::endl;
        }

        // 데이터베이스 존재 여부 확인 및 생성
        std::string checkDbSql = "SELECT 1 FROM pg_database WHERE datname = " + txn.quote(dbname);
        if (txn.exec(checkDbSql).empty()) {
            std::string createDbSql = "CREATE DATABASE " + txn.quote_identifier(dbname) + " WITH OWNER=" + txn.quote_identifier(user) + " ENCODING='UTF-8';";
            txn.exec(createDbSql);
            std::cout << "데이터베이스 '" << dbname << "' 생성 완료 (소유자: " << user << ")." << std::endl;
        } else {
            std::cout << "데이터베이스 '" << dbname << "' 이미 존재." << std::endl;
        }

        return true;
    } catch (const std::exception& e) {
        std::cerr << "데이터베이스/사용자 생성 중 예외 발생: " << e.what() << std::endl;
        return false;
    }
}

bool PgConnector::setupInitialDatabase() {
    // 여기에서 실제 작업할 데이터베이스와 사용자를 정의합니다.
    std::string targetDb = "measure_data_db";
    std::string targetUser = "data_user";
    std::string targetPass = "user_password";

    if (!getInstance().createDbAndUser(targetDb, targetUser, targetPass)) {
        return false;
    }

    // 초기 스키마 정의
    s_tableSchemas.clear();
    s_tableSchemas = {
        {"frequency_spectrum", {
            {"msg_id", "BIGINT", true},
            {"serial_num", "BIGINT", true},
            {"start_freq", "BIGINT"},
            {"end_freq", "BIGINT"},
            {"rbw_value", "DOUBLE PRECISION"},
            {"data_type", "SMALLINT"},
            {"data_count", "INTEGER"},
        }},
        {"time_domain_data", {
            {"msg_id", "BIGINT", true},
            {"serial_num", "BIGINT", true},
            {"capture_time", "TIMESTAMP WITHOUT TIME ZONE"},
            {"capture_time_ms", "TIMESTAMP WITHOUT TIME ZONE"},
            {"raw_data", "INTEGER[]"},
        }},
    };

    // DDL 작업은 해당 DB의 슈퍼유저나 소유자로 연결해서 수행하는 것이 일반적입니다.
    // 여기서는 `s_adminConnection`을 `targetDb`로 잠시 재연결하는 방식을 사용하겠습니다.
    s_adminConnection.reset(); // 기존 관리자 연결 해제
    std::string targetConnStr = "dbname=" + targetDb + " user=" + targetUser + " password=" + targetPass +
                                " hostaddr=127.0.0.1 port=5432";
    try {
        s_adminConnection.reset(new pqxx::connection(targetConnStr));
        if (!s_adminConnection->is_open()) {
            std::cerr << "DDL 작업용 데이터베이스 '" << targetDb << "' 연결 실패." << std::endl;
            return false;
        }
        std::cout << "DDL 작업을 위해 데이터베이스 '" << targetDb << "'에 연결되었습니다." << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "DDL 작업용 데이터베이스 연결 중 예외 발생: " << e.what() << std::endl;
        return false;
    }

    return createDefinedTables();
}

bool PgConnector::createDefinedTables() {
    if (!s_adminConnection || !s_adminConnection->is_open()) {
        std::cerr << "관리자 연결이 없으므로 초기 테이블을 생성할 수 없습니다." << std::endl;
        return false;
    }

    try {
        pqxx::work txn(*s_adminConnection);

        for (const auto& entry : s_tableSchemas) {
            const std::string& tableName = entry.first;
            const std::vector<ColumnDefinition>& columns = entry.second;

            std::string checkTableSql = "SELECT 1 FROM information_schema.tables WHERE table_name = " + txn.quote(tableName);
            if (txn.exec(checkTableSql).empty()) {
                std::ostringstream createTableSql;
                createTableSql << "CREATE TABLE " << txn.quote_identifier(tableName) << " (";
                createTableSql << txn.quote_identifier("id") << " BIGSERIAL PRIMARY KEY"; // 기본 ID 컬럼

                for (const auto& col : columns) {
                    createTableSql << ", " << txn.quote_identifier(col.name) << " " << col.type;
                    if (col.isNotNull) {
                        createTableSql << " NOT NULL";
                    }
                }
                createTableSql << ");";

                txn.exec(createTableSql.str());
                std::cout << "테이블 '" << tableName << "' 생성 완료." << std::endl;
            } else {
                std::cout << "테이블 '" << tableName << "' 이미 존재." << std::endl;
            }
        }
        txn.commit();
        return true;
    } catch (const std::exception& e) {
        std::cerr << "초기 테이블 생성 중 예외 발생: " << e.what() << std::endl;
        return false;
    }
}

bool PgConnector::establishThreadConnection(const std::string& dbName, const std::string& user, const std::string& password) {
    if (!s_threadConnection) {
        std::string connStr = "dbname=" + dbName + " user=" + user + " password=" + password +
                              " hostaddr=127.0.0.1 port=5432";
        try {
            s_threadConnection.reset(new pqxx::connection(connStr));
            if (s_threadConnection->is_open()) {
                std::cout << "스레드별 연결 성공: dbname=" << dbName << ", user=" << user << std::endl;
                return true;
            } else {
                std::cerr << "스레드별 연결 실패: dbname=" << dbName << ", user=" << user << std::endl;
                return false;
            }
        } catch (const std::exception& e) {
            std::cerr << "스레드별 연결 예외: " << e.what() << std::endl;
            s_threadConnection.reset(); // 연결 실패 시 포인터 초기화
            return false;
        }
    }
    return s_threadConnection->is_open(); // 이미 연결되어 있으면 상태 반환
}

void PgConnector::closeThreadConnection() {
    if (s_threadConnection) {
        if (s_threadConnection->is_open()) {
            s_threadConnection->disconnect();
            std::cout << "스레드별 연결 해제됨." << std::endl;
        }
        s_threadConnection.reset();
    }
}

bool PgConnector::insertData(const std::string& tableName, const std::map<std::string, std::string>& rowData) {
    if (!s_threadConnection || !s_threadConnection->is_open()) {
        std::cerr << "데이터 삽입 실패: 스레드별 연결이 열려있지 않습니다." << std::endl;
        return false;
    }

    try {
        pqxx::work txn(*s_threadConnection);
        std::ostringstream sqlCols, sqlVals;
        sqlCols << "INSERT INTO " << txn.quote_identifier(tableName) << " (";
        sqlVals << "VALUES (";

        bool first = true;
        for (const auto& pair : rowData) {
            if (!first) {
                sqlCols << ", ";
                sqlVals << ", ";
            }
            sqlCols << txn.quote_identifier(pair.first);
            sqlVals << txn.quote(pair.second);
            first = false;
        }
        sqlCols << ") ";
        sqlVals << ");";

        std::string fullSql = sqlCols.str() + sqlVals.str();
        txn.exec(fullSql);
        txn.commit();
        return true;
    } catch (const std::exception& e) {
        std::cerr << "데이터 삽입 중 예외 발생: " << e.what() << std::endl;
        return false;
    }
}

bool PgConnector::updateData(const std::string& tableName, const std::string& conditionCol, const std::string& conditionVal,
                            const std::map<std::string, std::string>& newValues) {
    if (!s_threadConnection || !s_threadConnection->is_open()) {
        std::cerr << "데이터 갱신 실패: 스레드별 연결이 열려있지 않습니다." << std::endl;
        return false;
    }

    try {
        pqxx::work txn(*s_threadConnection);
        std::ostringstream sql;
        sql << "UPDATE " << txn.quote_identifier(tableName) << " SET ";

        bool first = true;
        for (const auto& pair : newValues) {
            if (!first) {
                sql << ", ";
            }
            sql << txn.quote_identifier(pair.first) << " = " << txn.quote(pair.second);
            first = false;
        }
        sql << " WHERE " << txn.quote_identifier(conditionCol) << " = " << txn.quote(conditionVal) << ";";

        txn.exec(sql.str());
        txn.commit();
        return true;
    } catch (const std::exception& e) {
        std::cerr << "데이터 갱신 중 예외 발생: " << e.what() << std::endl;
        return false;
    }
}

std::vector<std::vector<std::string>> PgConnector::retrieveData(const std::string& tableName,
                                                            const std::vector<std::string>& columnsToSelect,
                                                            const std::string& conditionCol,
                                                            const std::string& conditionVal) {
    std::vector<std::vector<std::string>> results;
    if (!s_threadConnection || !s_threadConnection->is_open()) {
        std::cerr << "데이터 조회 실패: 스레드별 연결이 열려있지 않습니다." << std::endl;
        return results;
    }

    try {
        pqxx::nontransaction txn(*s_threadConnection);
        std::ostringstream sql;
        sql << "SELECT ";

        if (columnsToSelect.empty()) {
            sql << "*";
        } else {
            bool first = true;
            for (const std::string& col : columnsToSelect) {
                if (!first) sql << ", ";
                sql << txn.quote_identifier(col);
                first = false;
            }
        }
        sql << " FROM " << txn.quote_identifier(tableName);

        if (!conditionCol.empty() && !conditionVal.empty()) {
            sql << " WHERE " << txn.quote_identifier(conditionCol) << " = " << txn.quote(conditionVal);
        }
        sql << ";";

        pqxx::result res = txn.exec(sql.str());
        for (const auto& row : res) {
            std::vector<std::string> record;
            for (const auto& field : row) {
                record.push_back(field.c_str());
            }
            results.push_back(record);
        }
        return results;
    } catch (const std::exception& e) {
        std::cerr << "데이터 조회 중 예외 발생: " << e.what() << std::endl;
        return results;
    }
}

bool PgConnector::deleteData(const std::string& tableName, const std::string& conditionCol, const std::string& conditionVal) {
    if (!s_threadConnection || !s_threadConnection->is_open()) {
        std::cerr << "데이터 삭제 실패: 스레드별 연결이 열려있지 않습니다." << std::endl;
        return false;
    }

    try {
        pqxx::work txn(*s_threadConnection);
        std::ostringstream sql;
        sql << "DELETE FROM " << txn.quote_identifier(tableName);

        if (!conditionCol.empty() && !conditionVal.empty()) {
            sql << " WHERE " << txn.quote_identifier(conditionCol) << " = " << txn.quote(conditionVal);
        }
        sql << ";";

        txn.exec(sql.str());
        txn.commit();
        return true;
    } catch (const std::exception& e) {
        std::cerr << "데이터 삭제 중 예외 발생: " << e.what() << std::endl;
        return false;
    }
}

bool PgConnector::addTable(const std::string& tableName, const std::vector<ColumnDefinition>& columns) {
    if (!s_adminConnection || !s_adminConnection->is_open()) {
        std::cerr << "테이블 추가 실패: 관리자 연결이 열려있지 않습니다." << std::endl;
        return false;
    }

    try {
        pqxx::work txn(*s_adminConnection);
        std::string checkTableSql = "SELECT 1 FROM information_schema.tables WHERE table_name = " + txn.quote(tableName);
        if (!txn.exec(checkTableSql).empty()) {
            std::cout << "테이블 '" << tableName << "' 이미 존재합니다. 추가 작업을 건너뜀." << std::endl;
            return false;
        }

        std::ostringstream createTableSql;
        createTableSql << "CREATE TABLE " << txn.quote_identifier(tableName) << " (";
        createTableSql << txn.quote_identifier("id") << " BIGSERIAL PRIMARY KEY";

        for (const auto& col : columns) {
            createTableSql << ", " << txn.quote_identifier(col.name) << " " << col.type;
            if (col.isNotNull) {
                createTableSql << " NOT NULL";
            }
        }
        createTableSql << ");";
        txn.exec(createTableSql.str());
        txn.commit();

        s_tableSchemas[tableName] = columns; // 스키마 맵 업데이트
        std::cout << "테이블 '" << tableName << "'이(가) 성공적으로 추가되었습니다." << std::endl;
        return true;
    } catch (const std::exception& e) {
        std::cerr << "테이블 추가 중 예외 발생: " << e.what() << std::endl;
        return false;
    }
}

bool PgConnector::dropTable(const std::string& tableName) {
    if (!s_adminConnection || !s_adminConnection->is_open()) {
        std::cerr << "테이블 삭제 실패: 관리자 연결이 열려있지 않습니다." << std::endl;
        return false;
    }

    try {
        pqxx::work txn(*s_adminConnection);
        std::string sql = "DROP TABLE IF EXISTS " + txn.quote_identifier(tableName) + ";";
        txn.exec(sql);
        txn.commit();

        s_tableSchemas.erase(tableName); // 스키마 맵에서 제거
        std::cout << "테이블 '" << tableName << "'이(가) 성공적으로 삭제되었습니다." << std::endl;
        return true;
    } catch (const std::exception& e) {
        std::cerr << "테이블 삭제 중 예외 발생: " << e.what() << std::endl;
        return false;
    }
}

bool PgConnector::addColumnToTable(const std::string& tableName, const ColumnDefinition& column) {
    if (!s_adminConnection || !s_adminConnection->is_open()) {
        std::cerr << "컬럼 추가 실패: 관리자 연결이 열려있지 않습니다." << std::endl;
        return false;
    }

    try {
        pqxx::work txn(*s_adminConnection);
        std::string checkColumnSql = "SELECT 1 FROM information_schema.columns WHERE table_name = " + txn.quote(tableName) +
                                     " AND column_name = " + txn.quote(column.name);

        if (!txn.exec(checkColumnSql).empty()) {
            std::cout << "테이블 '" << tableName << "'에 컬럼 '" << column.name << "'이(가) 이미 존재합니다." << std::endl;
            return false;
        }

        std::ostringstream sql;
        sql << "ALTER TABLE " << txn.quote_identifier(tableName) << " ADD COLUMN "
            << txn.quote_identifier(column.name) << " " << column.type;
        if (column.isNotNull) {
            sql << " NOT NULL";
        }
        sql << ";";

        txn.exec(sql.str());
        txn.commit();
        std::cout << "테이블 '" << tableName << "'에 컬럼 '" << column.name << "'이(가) 성공적으로 추가되었습니다." << std::endl;
        // s_tableSchemas 업데이트 로직은 필요에 따라 추가
        return true;
    } catch (const std::exception& e) {
        std::cerr << "컬럼 추가 중 예외 발생: " << e.what() << std::endl;
        return false;
    }
}

bool PgConnector::removeColumnFromTable(const std::string& tableName, const std::string& columnName) {
    if (!s_adminConnection || !s_adminConnection->is_open()) {
        std::cerr << "컬럼 삭제 실패: 관리자 연결이 열려있지 않습니다." << std::endl;
        return false;
    }

    try {
        pqxx::work txn(*s_adminConnection);
        std::string checkColumnSql = "SELECT 1 FROM information_schema.columns WHERE table_name = " + txn.quote(tableName) +
                                     " AND column_name = " + txn.quote(columnName);

        if (txn.exec(checkColumnSql).empty()) {
            std::cout << "테이블 '" << tableName << "'에 컬럼 '" << columnName << "'이(가) 존재하지 않습니다." << std::endl;
            return false;
        }

        std::ostringstream sql;
        sql << "ALTER TABLE " << txn.quote_identifier(tableName) << " DROP COLUMN "
            << txn.quote_identifier(columnName) << ";";

        txn.exec(sql.str());
        txn.commit();
        std::cout << "테이블 '" << tableName << "'에서 컬럼 '" << columnName << "'이(가) 성공적으로 제거되었습니다." << std::endl;
        // s_tableSchemas 업데이트 로직은 필요에 따라 추가
        return true;
    } catch (const std::exception& e) {
        std::cerr << "컬럼 삭제 중 예외 발생: " << e.what() << std::endl;
        return false;
    }
}

3. 메인 파일 (main.cpp)

#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <map>
#include <chrono>

#include "PgConnector.h"

// 스레드별 데이터 삽입 함수
void performDataInsertion(int thread_id, const std::string& dbName, const std::string& user, const std::string& password) {
    PgConnector& connector = PgConnector::getInstance();
    if (!connector.establishThreadConnection(dbName, user, password)) {
        std::cerr << "스레드 " << thread_id << " - 데이터베이스 연결 실패." << std::endl;
        return;
    }

    std::string tableName = "frequency_spectrum";
    for (int i = 0; i < 50; ++i) { // 각 스레드에서 50개 데이터 삽입
        std::map<std::string, std::string> data;
        data["msg_id"] = std::to_string(1000 + thread_id);
        data["serial_num"] = std::to_string(20000 + i);
        data["start_freq"] = std::to_string(1000000 + i * 100);
        data["end_freq"] = std::to_string(2000000 + i * 100);
        data["rbw_value"] = "0.5";
        data["data_type"] = "1";
        data["data_count"] = "500";
        if (!connector.insertData(tableName, data)) {
            std::cerr << "스레드 " << thread_id << " - 데이터 삽입 실패: msg_id=" << data["msg_id"] << std::endl;
        } else {
            //std::cout << "스레드 " << thread_id << " - 데이터 삽입 성공: msg_id=" << data["msg_id"] << std::endl;
        }
    }
    connector.closeThreadConnection();
}

// 스레드별 데이터 삭제 함수
void performDataDeletion(int thread_id, const std::string& dbName, const std::string& user, const std::string& password, const std::string& target_msg_id) {
    PgConnector& connector = PgConnector::getInstance();
    if (!connector.establishThreadConnection(dbName, user, password)) {
        std::cerr << "스레드 " << thread_id << " - 데이터베이스 연결 실패." << std::endl;
        return;
    }

    std::string tableName = "frequency_spectrum";
    if (connector.deleteData(tableName, "msg_id", target_msg_id)) {
        std::cout << "스레드 " << thread_id << " - msg_id=" << target_msg_id << " 데이터 삭제 성공." << std::endl;
    } else {
        std::cerr << "스레드 " << thread_id << " - msg_id=" << target_msg_id << " 데이터 삭제 실패." << std::endl;
    }
    connector.closeThreadConnection();
}

void performDataQuery(const std::string& dbName, const std::string& user, const std::string& password) {
    PgConnector& connector = PgConnector::getInstance();
    if (!connector.establishThreadConnection(dbName, user, password)) {
        std::cerr << "메인 스레드 - 데이터베이스 연결 실패." << std::endl;
        return;
    }

    std::string tableName = "frequency_spectrum";
    std::cout << "\n모든 데이터 조회 (frequency_spectrum):" << std::endl;
    std::vector<std::vector<std::string>> all_data = connector.retrieveData(tableName);
    for (const auto& row : all_data) {
        for (const auto& field : row) {
            std::cout << field << "\t";
        }
        std::cout << std::endl;
    }

    std::cout << "\nmsg_id='1001' 데이터 조회 (frequency_spectrum):" << std::endl;
    std::vector<std::vector<std::string>> specific_data = connector.retrieveData(tableName, {}, "msg_id", "1001");
    for (const auto& row : specific_data) {
        for (const auto& field : row) {
            std::cout << field << "\t";
        }
        std::cout << std::endl;
    }
    connector.closeThreadConnection();
}


int main() {
    // PgConnector 인스턴스 초기화 및 DB/테이블 생성 (싱글톤 생성자에서 처리됨)
    PgConnector& connectorInstance = PgConnector::getInstance();

    std::string dbName = "measure_data_db";
    std::string user = "data_user";
    std::string password = "user_password";

    // 여러 스레드를 이용한 데이터 삽입
    std::cout << "--- 다중 스레드 데이터 삽입 시작 ---" << std::endl;
    std::vector<std::thread> insertion_threads;
    for (int i = 0; i < 3; ++i) {
        insertion_threads.emplace_back(performDataInsertion, i, dbName, user, password);
    }

    for (auto& t : insertion_threads) {
        t.join();
    }
    std::cout << "--- 다중 스레드 데이터 삽입 완료 ---" << std::endl;

    // 데이터 조회 예시
    performDataQuery(dbName, user, password);

    // 데이터 갱신 예시
    std::cout << "\n--- 데이터 갱신 시작 ---" << std::endl;
    std::string updateTableName = "frequency_spectrum";
    std::map<std::string, std::string> updates = {
        {"rbw_value", "1.0"},
        {"data_count", "1000"}
    };
    if (connectorInstance.establishThreadConnection(dbName, user, password)) {
        if (connectorInstance.updateData(updateTableName, "msg_id", "1000", updates)) {
            std::cout << "msg_id '1000' 데이터 갱신 성공." << std::endl;
        } else {
            std::cerr << "msg_id '1000' 데이터 갱신 실패." << std::endl;
        }
        connectorInstance.closeThreadConnection();
    } else {
        std::cerr << "갱신을 위한 연결 실패." << std::endl;
    }
    std::cout << "--- 데이터 갱신 완료 ---" << std::endl;

    // 갱신된 데이터 확인
    performDataQuery(dbName, user, password);

    // 데이터 삭제 예시 (단일 스레드 또는 다중 스레드)
    std::cout << "\n--- 데이터 삭제 시작 ---" << std::endl;
    std::vector<std::thread> deletion_threads;
    deletion_threads.emplace_back(performDataDeletion, 5, dbName, user, password, "1001");
    deletion_threads.emplace_back(performDataDeletion, 6, dbName, user, password, "1002");

    for (auto& t : deletion_threads) {
        t.join();
    }
    std::cout << "--- 데이터 삭제 완료 ---" << std::endl;

    // 삭제 후 데이터 확인
    performDataQuery(dbName, user, password);

    // 추가 테이블 생성 예시
    std::cout << "\n--- 새 테이블 추가 시작 ---" << std::endl;
    std::vector<ColumnDefinition> newTableCols = {
        {"sensor_id", "INTEGER", true},
        {"location", "VARCHAR(255)"},
        {"temperature", "NUMERIC(5,2)"}
    };
    connectorInstance.addTable("sensor_readings", newTableCols);
    std::cout << "--- 새 테이블 추가 완료 ---" << std::endl;

    // 새 컬럼 추가 예시
    std::cout << "\n--- 컬럼 추가 시작 ---" << std::endl;
    ColumnDefinition newCol = {"humidity", "NUMERIC(5,2)"};
    connectorInstance.addColumnToTable("sensor_readings", newCol);
    std::cout << "--- 컬럼 추가 완료 ---" << std::endl;

    // 컬럼 삭제 예시
    std::cout << "\n--- 컬럼 삭제 시작 ---" << std::endl;
    connectorInstance.removeColumnFromTable("sensor_readings", "humidity");
    std::cout << "--- 컬럼 삭제 완료 ---" << std::endl;

    // 테이블 삭제 예시
    std::cout << "\n--- 테이블 삭제 시작 ---" << std::endl;
    connectorInstance.dropTable("sensor_readings");
    std::cout << "--- 테이블 삭제 완료 ---" << std::endl;

    return 0;
}

4. 컴파일

위 코드들을 main.cppPgConnector.cpp로 각각 저장한 후, 다음 명령어를 사용하여 컴파일합니다:

g++ -pthread --std=c++17 -o pg_client_demo main.cpp PgConnector.cpp -lpqxx -lpq

이 명령어는 -pthread 옵션으로 스레드 지원을 활성화하고, --std=c++17로 C++17 표준을 사용합니다. -lpqxx-lpq는 각각 libpqxxlibpq 라이브러리에 연결하는 옵션입니다.

태그: PostgreSQL C++ libpqxx linux Database

8월 18일 00:56에 게시됨