Computer >> 컴퓨터 >  >> 프로그래밍 >> C 프로그래밍

C/C++에서 SQLite 데이터베이스 활용하기: 설치부터 CRUD 실습까지

이 글에서는 C/C++ 프로그램에서 SQLite를 사용하는 방법을 단계별로 살펴봅니다. 라이브러리 설치부터 주요 인터페이스 API, 그리고 데이터베이스 연결, 테이블 생성, 데이터 삽입과 조회까지 실제 동작하는 예제 코드와 함께 자세히 설명합니다.

설치

C/C++ 프로그램에서 SQLite를 사용하기 전에, 먼저 개발 환경에 SQLite 라이브러리가 올바르게 설정되어 있는지 확인해야 합니다. 설치 과정에 대한 자세한 내용은 SQLite 공식 문서의 설치(Installation) 챕터를 참고하시기 바랍니다. 대부분의 리눅스 배포판에서는 aptyum 같은 패키지 관리자로 간단히 설치할 수 있으며, macOS는 Homebrew를 통해 설치할 수 있습니다.

C/C++ 인터페이스 API

다음은 C/C++ 프로그램에서 SQLite 데이터베이스를 다룰 때 필수적으로 알아야 할 주요 인터페이스 함수들입니다. 더 복잡하고 고도화된 애플리케이션을 개발하려면 SQLite 공식 문서를 참고하는 것이 좋습니다.

번호API 및 설명
1
sqlite3_open(const char *filename, sqlite3 **ppDb)
지정한 SQLite 데이터베이스 파일에 대한 연결을 열고, 이후 다른 SQLite 함수들이 사용할 수 있는 데이터베이스 연결 객체를 반환합니다.

filename 인자가 NULL이거나 ':memory:'인 경우, sqlite3_open()은 RAM에 세션 동안만 유지되는 인메모리(in-memory) 데이터베이스를 생성합니다.

파일명이 NULL이 아니라면 해당 값을 사용해 데이터베이스 파일을 열려고 시도하고, 같은 이름의 파일이 존재하지 않으면 새로운 데이터베이스 파일을 생성합니다.
2
sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void *data, char **errmsg)
sql 인자로 전달된 SQL 명령을 빠르고 간편하게 실행할 수 있는 함수입니다. sql 인자에는 여러 개의 SQL 명령을 포함할 수 있습니다.

첫 번째 인자 sqlite3는 열려 있는 데이터베이스 객체이고, sqlite_callback은 콜백 함수로 data가 첫 번째 인자로 전달됩니다. errmsg는 함수 실행 중 발생한 오류 메시지를 담아 반환받는 용도로 사용됩니다.

sqlite3_exec()는 sql 인자에 포함된 모든 명령을 문자열 끝에 도달하거나 오류가 발생할 때까지 순서대로 파싱하고 실행합니다.
3
sqlite3_close(sqlite3*)
sqlite3_open()으로 이전에 열었던 데이터베이스 연결을 종료합니다. 연결을 닫기 전에 해당 연결과 관련된 모든 prepared statement(준비된 구문)는 반드시 finalize(종료) 처리되어야 합니다.

아직 finalize되지 않은 쿼리가 남아 있다면 sqlite3_close()는 SQLITE_BUSY를 반환하며, "Unable to close due to unfinalized statements" 오류 메시지를 출력합니다.

데이터베이스 연결하기

다음 C 코드는 기존 데이터베이스에 연결하는 방법을 보여줍니다. 만약 해당 데이터베이스가 존재하지 않으면 새로 생성되며, 최종적으로 데이터베이스 객체가 반환됩니다.

예제 코드

#include <stdio.h>
#include <sqlite3.h>

int main(int argc, char* argv[]) {
    sqlite3 *db;
    char *zErrMsg = 0;
    int rc;

    /* 데이터베이스 열기 */
    rc = sqlite3_open("test.db", &db);

    if( rc ) {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        return(0);
    } else {
        fprintf(stderr, "Opened database successfully\n");
    }
    sqlite3_close(db);
}

실행 결과

$gcc test.c -l sqlite3
$./a.out
Opened database successfully

테이블 생성하기

다음 C 코드는 앞서 생성한 test.db 데이터베이스 안에 COMPANY라는 테이블을 만드는 예제입니다.

예제 코드

#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>

static int callback(void *NotUsed, int argc, char **argv, char **azColName) {
    int i;
    for(i = 0; i<argc; i++) {
        printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
    }
    printf("\n");
    return 0;
}

int main(int argc, char* argv[]) {
    sqlite3 *db;
    char *zErrMsg = 0;
    int rc;
    char *sql;

    /* 데이터베이스 열기 */
    rc = sqlite3_open("test.db", &db);

    if( rc ) {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        return(0);
    } else {
        fprintf(stdout, "Opened database successfully\n");
    }

    /* SQL문 작성 */
    sql = "CREATE TABLE COMPANY(" \
          "ID INT PRIMARY KEY NOT NULL," \
          "NAME TEXT NOT NULL," \
          "AGE INT NOT NULL," \
          "ADDRESS CHAR(50)," \
          "SALARY REAL );";

    /* SQL문 실행 */
    rc = sqlite3_exec(db, sql, callback, 0, &zErrMsg);

    if( rc != SQLITE_OK ) {
        fprintf(stderr, "SQL error: %s\n", zErrMsg);
        sqlite3_free(zErrMsg);
    } else {
        fprintf(stdout, "Table created successfully\n");
    }
    sqlite3_close(db);
    return 0;
}

실행 결과 (데이터베이스 파일 확인):

-rwxr-xr-x. 1 root root 9567 May 8 02:31 a.out
-rw-r--r--. 1 root root 1207 May 8 02:31 test.c
-rw-r--r--. 1 root root 3072 May 8 02:31 test.db

위 출력에서 test.db 파일이 실제로 생성된 것을 확인할 수 있습니다.

INSERT 작업 (데이터 삽입)

다음 C 코드는 위 예제에서 생성한 COMPANY 테이블에 레코드를 추가하는 방법을 보여줍니다.

예제 코드

#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>

static int callback(void *NotUsed, int argc, char **argv, char **azColName) {
    int i;
    for(i = 0; i<argc; i++) {
        printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
    }
    printf("\n");
    return 0;
}

int main(int argc, char* argv[]) {
    sqlite3 *db;
    char *zErrMsg = 0;
    int rc;
    char *sql;

    /* 데이터베이스 열기 */
    rc = sqlite3_open("test.db", &db);

    if( rc ) {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        return(0);
    } else {
        fprintf(stderr, "Opened database successfully\n");
    }

    /* SQL문 작성 */
    sql = "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " \
          "VALUES (1, 'Paul', 32, 'California', 20000.00 ); " \
          "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) " \
          "VALUES (2, 'Allen', 25, 'Texas', 15000.00 ); " \
          "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)" \
          "VALUES (3, 'Teddy', 23, 'Norway', 20000.00 );" \
          "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY)" \
          "VALUES (4, 'Mark', 25, 'Rich-Mond ', 65000.00 );";

    /* SQL문 실행 */
    rc = sqlite3_exec(db, sql, callback, 0, &zErrMsg);

    if( rc != SQLITE_OK ) {
        fprintf(stderr, "SQL error: %s\n", zErrMsg);
        sqlite3_free(zErrMsg);
    } else {
        fprintf(stdout, "Records created successfully\n");
    }
    sqlite3_close(db);
    return 0;
}

실행 결과

Opened database successfully
Records created successfully

SELECT 작업 (데이터 조회)

실제 레코드를 조회하는 예제로 넘어가기 전에, 예제에서 사용 중인 콜백(callback) 함수에 대해 자세히 알아보겠습니다. 이 콜백 함수는 SELECT 문의 결과를 가져오는 역할을 하며, 다음과 같이 선언됩니다.

typedef int (*sqlite3_callback)(
    void*, /* sqlite3_exec()의 네 번째 인자로 전달된 데이터 */
    int,   /* 한 행(row)의 컬럼 수 */
    char**, /* 행의 각 필드 값을 나타내는 문자열 배열 */
    char**  /* 각 컬럼의 이름을 나타내는 문자열 배열 */
);

위 콜백 함수가 sqlite3_exec()의 세 번째 인자로 전달되면, SQLite는 sql 인자 내에서 실행되는 각 SELECT 문이 처리하는 레코드마다 이 콜백 함수를 호출합니다.

다음 C 코드는 앞서 생성한 COMPANY 테이블에서 레코드를 가져와 화면에 출력하는 방법을 보여줍니다.

예제 코드

#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>

static int callback(void *data, int argc, char **argv, char **azColName) {
    int i;
    fprintf(stderr, "%s: ", (const char*)data);
    for(i = 0; i<argc; i++) {
        printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
    }
    printf("\n");
    return 0;
}

int main(int argc, char* argv[]) {
    sqlite3 *db;
    char *zErrMsg = 0;
    int rc;
    char *sql;
    const char* data = "Callback function called";

    /* 데이터베이스 열기 */
    rc = sqlite3_open("test.db", &db);

    if( rc ) {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        return(0);
    } else {
        fprintf(stderr, "Opened database successfully\n");
    }

    /* SQL문 작성 */
    sql = "SELECT * from COMPANY";

    /* SQL문 실행 */
    rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);

    if( rc != SQLITE_OK ) {
        fprintf(stderr, "SQL error: %s\n", zErrMsg);
        sqlite3_free(zErrMsg);
    } else {
        fprintf(stdout, "Operation done successfully\n");
    }
    sqlite3_close(db);
    return 0;
}

실행 결과

Opened database successfully
Callback function called: ID = 1
NAME = Paul
AGE = 32
ADDRESS = California
SALARY = 20000.0

Callback function called: ID = 2
NAME = Allen
AGE = 25
ADDRESS = Texas
SALARY = 15000.0

Callback function called: ID = 3
NAME = Teddy
AGE = 23
ADDRESS = Norway
SALARY = 20000.0

Callback function called: ID = 4
NAME = Mark
AGE = 25
ADDRESS = Rich-Mond
SALARY = 65000.0

Operation done successfully

출력 결과에서 볼 수 있듯이, SELECT 문으로 조회된 각 레코드마다 콜백 함수가 호출되어 해당 행의 컬럼 이름과 값이 순서대로 출력됩니다. 이처럼 sqlite3_exec()와 콜백 함수 조합은 간단한 조회 작업에 매우 유용합니다.