새소식

DB/SQLite3

SQLite, C++, SELECT 예제 첨삭

  • -

 

개요

 

C/C++ 에서 DB를 사용하는 방법은

TCP/IP 소켓 사용법, 파일 입출력 사용법과 거의 동일한 실행 순서를 가진다.

 

기반은 리눅스 파일 디스크립터(참고 링크), 윈도우에서는 핸들이라고 한다,를 이용한다.

SQLite 라이브러리는 C언어로 되어 있기 때문에, C++ 코드로 랩핑하여 사용하거나, 그냥 C 코드를 사용해도 무방하다.

 

가급적 SQLite3 C/C++ Interface 공식문서를 참고하길 권장한다.

 

 

 

 

참고링크

 

공식문서

룬북.dev

'프로그래밍 놀이터/Database' 카테고리의 글 목록 (tistory.com)

SQLite 데이터베이스 C/C++에서 사용하기 1: 기본 (번역) - 맹스비 블로그 (mangsby.com)

 

 

 

SQLite 프로세스 요약

아래 예제들은 원하는 방식으로 수정해서 사용한다.

 

 

SQLite 프로세스 1

 

1. SQLite 연결: sqlite3_open(const char *filename, sqlite3 **ppDb)

2.1 SQLite 실행 요청 준비: int sqlite3_prepare_v2( sqlite3 *db, const char *zSql,  int nByte,  sqlite3_stmt **ppStmt, const char **pzTail );
2.2 SQLite 실행: int sqlite3_step(sqlite3_stmt*);
2.3 SQLite 실행 후 한줄의 컬럼수 찾기 sqlite3_column_count( );
2.4 SQLite 실행 후 한줄의 컬럼수 만큼 데이터 가져오기  const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);
2.5 SQLite 실행 요청 준비 작업 리셋: int sqlite3_reset(sqlite3_stmt *pStmt);

3. SQLite 종료: sqlite3_close(sqlite3*)

 

 

SQLite 프로세스 2

 

1. SQLite 연결: sqlite3_open(const char *filename, sqlite3 **ppDb)

2. SQLite 쿼리 실행: sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void *data, char **errmsg)

3. SQLite 종료: sqlite3_close(sqlite3*)SQLite 연결: sqlite3_open(const char *filename, sqlite3 **ppDb)

 

 

 

 

SQLite 프로세스 상세

 

1. SQLite 연결하기

 

sqlite3_open(const char *filename, sqlite3 **ppDb)

 

 

 

C 원본 코드

 

int sqlite3_open(
  const char *filename,   /* Database filename (UTF-8) */
  sqlite3 **ppDb          /* OUT: SQLite db handle */
);

 

SQLite 데이터베이스 파일에 대한 연결 개체를 반환합니다. 

filename 인자가 NULL이나 ':memory:'이면, sqlite3_open()는 데이터베이스를 RAM에 만듭니다. 

파일 이름이 NULL이 아닌 경우, 데이터베이스 파일을 열려고 시도합니다. 

해당 이름의 파일이 없는 경우, 새 데이터베이스 파일을 엽니다.

기본 인코딩은  UTF-8로 해석(File Name)됩니다.

 

 

C++ 랩핑 코드

 

//sqlite3를 연결하는 C언어로 된 sqlite3_open( ) 함수를 랩핑해서 C++ 코드를 만듭니다.

// _sqlite3_open 이름을 사용하고
bool SQLiteCpp::_sqlite3_open(std::string dir)
{
	// C언어로 된 sqlite3_open( ) 함수가 정상적으로 작동하지 않으면
    if (sqlite3_open(dir.c_str(), &db) != SQLITE_OK)
    {
    	// 에러 메시지를 출력하고
        std::cout << "Cannot open database: " << sqlite3_errmsg(db) << std::endl;
        // 연결 시도한 db 객체를 sqlite3_close() 함수를 이용해 닫습니다.
        sqlite3_close(db);
		
        // 동작이 실패한 함수를 종료시킵니다.
        return false;
    }

    // 테이블이 없다면, 
    if (this->IsTableExist() == false)
    {
    	// 테이블을 생성합니다. 공식 문서의 CreateTable() 함수를 참고하세요.
        this->CreateTable();
    }
    
    // _sqlite3_open() 함수를 정상 종료시킵니다.
    return true;
}

 

 

 

 

2. SQLite 쿼리 실행 방법 (첫번째)

 sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void *data, char **errmsg)

 

 

 

C 원본 코드

 

int sqlite3_exec(
  sqlite3*,                                  /* An open database */
  const char *sql,                           /* SQL to be evaluated */
  int (*callback)(void*,int,char**,char**),  /* Callback function */
  void *,                                    /* 1st argument to callback */
  char **errmsg                              /* Error msg written here */
);

 

 

SQL 쿼리 실행 요청 을 빠르고 쉽게 실행할 수 있는 방법입니다.

sqlite3_exec() 인터페이스는 sqlite3_prepare_v2(), sqlite3_step() 및 sqlite3_finalize()를 둘러싼 편리한 래퍼입니다.

이말인즉, sqlite3_prepare_v2(), sqlite3_step() 및 sqlite3_finalize()를 함수를 사용하면 더 상세하게 쿼리 실행 요청을 할 수 있습니다.

 

첫 번째 인수는 연결된 열린 sqlite3 데이터베이스 객체

두 번째 인수는 SQL 쿼리

세 번째는 콜백

네 번째는 콜백의 첫번째 인자

다섯 번째는 에러 메시지

 

이외에 비슷한 함수로 sqlite3_get_table() 함수가 있다.

sqlite3_exec() 함수, sqlite3_get_table() 함수로도 간단히 처리할 수 있다

 

 

 

 

2. SQLite 쿼리 실행  방법 (두번째)

 

2.1 준비

 

int sqlite3_prepare_v2( sqlite3 *db, const char *zSql,  int nByte,  sqlite3_stmt **ppStmt, const char **pzTail );

 

 

 

C 원본 코드

 

int sqlite3_prepare_v2(
  sqlite3 *db,            /* Database handle */
  const char *zSql,       /* SQL statement, UTF-8 encoded */
  int nByte,              /* Maximum length of zSql in bytes. */
  sqlite3_stmt **ppStmt,  /* OUT: Statement handle */
  const char **pzTail     /* OUT: Pointer to unused portion of zSql */
);

 

SQL 문을 실행하려면 먼저 바이트 코드로 컴파일해야 합니다

 

첫 번째 인수 "db"는 이전에 sqlite3_open(), sqlite3_open_v2() 또는 sqlite3_open16()에 실행 성공한 후 돌려받은 객체 입니다. 간단하게, 데이터베이스 연결되어 있어야 합니다.

 

두 번째 인수인 "zSql"은 컴파일되고 바이트코드로 인코딩될 SQL 명령문입니다

 

세 번째 인수인 nByte 음수이면 SQL 첫 번째 종결문자까지 읽습니다.

nByte 매개 변수를 전달하면 약간의 성능 이점이 있습니다. 

 

네 번째 *ppStmt 는 SQL 이 컴파일 된 준비된 명령문을 가리 킵니다.

 

pzTail이 NULL이 아닌 경우 *pzTail은 첫 번째 바이트를 가리키도록 만들어집니다 zSql에서 첫 번째 SQL 문의 끝을 지나서. 이러한 루틴만 zSql에서 첫 번째 명령문을 컴파일하므로 *pzTail은 다음을 가리킵니다. 컴파일되지 않은 상태로 남아 있는 항목입니다.

 

 sqlite3_step()를 사용하여 실행됩니다.

오류가 있는 경우 *ppStmt가 NULL로 설정됩니다.

입력 텍스트에 SQL이 포함되어 있지 않은 경우(입력이 비어 있는 경우 string 또는 주석)이면 *ppStmt 가 NULL로 설정됩니다.

호출 프로시저는 컴파일된 sqlite3_finalize()를 사용한 SQL 문입니다. 

 

성공하면, 루틴의 sqlite3_prepare()는 SQLITE_OK를 반환합니다.

그렇지 않으면 오류 코드가 반환됩니다.

 

 

 

 

2.2 쿼리 실행

 

int sqlite3_step(sqlite3_stmt*);

 

 

 

C 원본 코드

 

int sqlite3_step(sqlite3_stmt*);

 

 

레거시 인터페이스에서 반환 값 SQLITE_BUSY, SQLITE_DONE, SQLITE_ROW, SQLITE_ERROR 또는 SQLITE_MISUSE입니다.

 

SQLITE_BUSY 

데이터베이스가 잠긴상태

 

SQLITE_DONE 

명령문 실행 완료 되었음을 의미합니다. 이 상태에서 sqlite3_step()를 다시 호출해서는 안 됩니다.  먼저 sqlite3_reset()를 호출하여 초기 상태로 되돌려야 합니다.

 

SQLITE_ROW 

실행 중인 SQL 문이 데이터를 반환하는 경우 새 데이터 행을 처리할 준비가 될 때마다 SQLITE_ROW 반환됩니다. sqlite3_step()가 다시 호출되어 다음 데이터 행을 검색합니다.

 

SQLITE_ERROR 

런타임 오류가 발생했습니다. sqlite3_step()를 다시 호출해서는 안 됩니다.

구체적인 오류 코드(예: SQLITE_INTERRUPT, SQLITE_SCHEMA, SQLITE_CORRUPT 등) 

"v2" 인터페이스에서, 보다 구체적인 오류 코드는 sqlite3_step()에 의해 직접 반환됩니다.

 

SQLITE_MISUSE 

this 루틴이 부적절하게 호출되었음을 의미합니다. .

 

 

 

 

2.3 쿼리 실행 결과에서 한줄의 컬럼 개수 찾기

sqlite3_column_count( );

 

 

 

C 코드

int sqlite3_column_count(sqlite3_stmt *pStmt);

 

 

 

 

2.4 쿼리 실행 결과에서 텍스트 가져오기

 

const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);

 

 

 

const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol);

 

BLOB의 크기 또는 UTF-8 TEXT 결과(바이트)

 

sqlite3_column_blob BLOB 결과
sqlite3_column_double REAL 결과
sqlite3_column_int 32비트 INTEGER 결과
sqlite3_column_int64 64비트 INTEGER 결과
sqlite3_column_text UTF-8 TEXT 결과
sqlite3_column_text16 UTF-16 TEXT 결과
sqlite3_column_value 보호되지 않는 sqlite3_value 개체로서의 결과입니다.
     
sqlite3_column_bytes BLOB의 크기 또는 UTF-8 TEXT 결과(바이트)
sqlite3_column_bytes16   →   UTF-16의 크기 TEXT(바이트)
sqlite3_column_type 기본값 결과의 데이터 유형

 

 

 

 

2.5 준비된 명령문 개체 재설정

 

int sqlite3_reset(sqlite3_stmt *pStmt);

 

 

int sqlite3_reset(sqlite3_stmt *pStmt);

 

sqlite3_reset() 함수는  준비된 명령문 객체를 다시 실행할 준비가 된 초기 상태로 재설정

 

 

 

 

2.6 준비된 명령문 객체 삭제

 

int sqlite3_finalize(sqlite3_stmt *pStmt);

 

 

 

int sqlite3_finalize(sqlite3_stmt *pStmt);

 

준비된 명령문을 삭제합니다.

 

 

 

 

테이블의 모든 값 가져오기 C++ 랩핑 코드

 

// sqlite3_prepare_v2()
// sqlite3_step()
// sqlite3_finalize()
// 을 사용합니다.

// 아..
std::vector<std::vector<std::string>>  SQLiteCpp::GetAllRecords()
{
	// DB 테이블 값이 담길 벡터 설정
    std::vector<std::vector<std::string>> result;
    
    // 쿼리문 바이트 코드 담길 객체 설정
    sqlite3_stmt *stmt;
    
    // 쿼리 문장
    const char *sql = QUERY_TABLE_SELECT_ALL.c_str();

	// 실행 쿼리 출력
    std::cout << sql << std::endl;

	// 쿼리 준비
    if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK)
    	// 실패시 출력
        std::cout << "sqlite3_prepare_v2() Fail" << std::endl;

	// 준비된 쿼리 실행
    // 수행 성공 SQLITE_DONE, 반환 SELECT 경우 SQLITE_ROW
    while (sqlite3_step(stmt) == SQLITE_ROW)
    {
		// 한 행에 해당하는 값을 담을 벡터를 선언하고
        std::vector<std::string> temp;
        // 한 행의 컬럼 개수만큼 벡터에 담기
        for (size_t i = 0; i < sqlite3_column_count(stmt); i++) 
        {
            std::string str = (const char*)sqlite3_column_text(stmt, i);
            temp.push_back(str);
        }
		// 한 행 만큼의 데이터를 이중벡터에 담기...
        result.push_back(temp);
    }

	// sqlite3_prepare_v2() 를 리셋시킨다.
    sqlite3_reset(stmt);
    
    // stmt
    sqlite3_finalize(stmt);

    return result;
}

 

 

 

3. SQLite 종료

 

sqlite3_close(sqlite3*);

 

// 연결 시도한 db 객체를 sqlite3_close() 함수를 이용해 닫습니다.
bool SQLiteCpp::_sqlite3_close()
{
    return (sqlite3_close(db) == SQLITE_OK);
}

 

 

 

 


예제

 

DB 데이터 생성 스크립트

CREATE TABLE Members (
	code INTEGER PRIMARY KEY AUTOINCREMENT,
	id TEXT(12),
	pw TEXT(18),
	fname TEXT,
	lname TEXT,
	ph TEXT,
	email TEXT,
	disabled NUMERIC
);

INSERT INTO Members
(code, id, pw, fname, lname, ph, email, disabled)
VALUES
(1, 'aaa', '1111', 'Richard', 'Choi', '010-1234', 'abcd@a.com', 0),
(2, 'bbb', '222', 'Olivia', 'Kim', '017-9999', 'zz@b.com', 0),
(3, 'ccc', '33', 'Rose', 'Park', '019-6788', 'vvv@c.com', 1)
(4, 'd', '4', 'C++', 'Lee', '054-123', 'a@a.com', 0),
(5, 'e', '55', 'Python', 'Ryu', '02-88', 'b@b.com', 0),
(6, 'f', '666', 'Java', 'Jo', '070-876', 'c@c.com', 1);

 

 

SQLiteCpp.h

 

#pragma once

#include <iostream>
#include <sqlite3.h>
#include <vector>

static const std::string DIR = "/home/basiclike/VSCode/test/DB/DB_SQLite";
static const std::string QUERY_TABLE_CREATE = "CREATE TABLE Members (code INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT(12), pw TEXT(18), fname TEXT, lname TEXT, ph TEXT, email TEXT, disabled NUMERIC);";
static const std::string QUERY_TABLE_EXIST = "SELECT name FROM sqlite_master WHERE type='table' AND name='Members';";
static const std::string QUERY_TABLE_DROP = "DROP TABLE IF EXISTS Members;";
static const std::string QUERY_TABLE_EXIST_RECORD = "SELECT FilePath FROM Members WHERE FilePath=? AND CheckSum=? AND Version=?;";
static const std::string QUERY_TABLE_DELETE_RECORD = "DELETE FROM Members WHERE FilePath=?;";
static const std::string QUERY_TABLE_INSERT_RECORD = "INSERT INTO Members VALUES(?, ?, ?);";
static const std::string QUERY_TABLE_SELECT_ALL = "SELECT * FROM Members;";
static const std::string QUERY_DB_CLEAN_UP = "VACUUM;";
static const std::string QUERY_DB_BEGIN___ = "BEGIN;";
static const std::string QUERY_DB_COMMIT__ = "COMMIT;";
static const std::string QUERY_DB_ROLLBACK = "ROLLBACK;";

class SQLiteCpp
{
private:
    sqlite3 *db;

private:
    bool CreateTable();
    bool DropTable();
    bool IsTableExist();

    bool Begin();
    bool Commit();
    bool Rollback();

public:
    bool _sqlite3_open(std::string dbName);
    bool _sqlite3_close();
    std::vector<std::vector<std::string>>  GetAllRecords();
    // bool _insert(std::string dir, std::string checksum, void *binaryData, int size);
    // bool _delete(std::string key);

};

 

 

SQLiteCpp.cpp

 

#include <stdio.h>
#include <iostream>
#include <sqlite3.h>
#include <vector>
#include "SQLiteCpp.h"


//sqlite3를 연결하는 C언어로 된 sqlite3_open( ) 함수를 랩핑해서 C++ 코드를 만듭니다.

// _sqlite3_open 이름을 사용하고
bool SQLiteCpp::_sqlite3_open(std::string dir)
{
	// C언어로 된 sqlite3_open( ) 함수가 정상적으로 작동하지 않으면
    if (sqlite3_open(dir.c_str(), &db) != SQLITE_OK)
    {
    	// 에러 메시지를 출력하고
        std::cout << "Cannot open database: " << sqlite3_errmsg(db) << std::endl;
        // 연결 시도한 db 객체를 sqlite3_close() 함수를 이용해 닫습니다.
        sqlite3_close(db);
		
        // 동작이 실패한 함수를 종료시킵니다.
        return false;
    }

    // 테이블이 없다면, 
    if (this->IsTableExist() == false)
    {
    	// 테이블을 생성합니다. 공식 문서의 CreateTable() 함수를 참고하세요.
        this->CreateTable();
    }
    
    // _sqlite3_open() 함수를 정상 종료시킵니다.
    return true;
}

// 연결 시도한 db 객체를 sqlite3_close() 함수를 이용해 닫습니다.
bool SQLiteCpp::_sqlite3_close()
{
    return (sqlite3_close(db) == SQLITE_OK);
}

// sqlite3_prepare_v2()
// sqlite3_step()
// sqlite3_finalize()
// 을 사용합니다.

// 아..
std::vector<std::vector<std::string>>  SQLiteCpp::GetAllRecords()
{
	// DB 테이블 값이 담길 벡터 설정
    std::vector<std::vector<std::string>> result;
    
    // 쿼리문 바이트 코드 담길 객체 설정
    sqlite3_stmt *stmt;
    
    // 쿼리 문장
    const char *sql = QUERY_TABLE_SELECT_ALL.c_str();

	// 실행 쿼리 출력
    std::cout << sql << std::endl;

	// 쿼리 준비
    if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK)
    	// 실패시 출력
        std::cout << "sqlite3_prepare_v2() Fail" << std::endl;

	// 준비된 쿼리 실행
    // 수행 성공 SQLITE_DONE, 반환 SELECT 경우 SQLITE_ROW
    while (sqlite3_step(stmt) == SQLITE_ROW)
    {
		// 한 행에 해당하는 값을 담을 벡터를 선언하고
        std::vector<std::string> temp;
        // 한 행의 컬럼 개수만큼 벡터에 담기
        for (size_t i = 0; i < sqlite3_column_count(stmt); i++) 
        {
            std::string str = (const char*)sqlite3_column_text(stmt, i);
            temp.push_back(str);
        }
		// 한 행 만큼의 데이터를 이중벡터에 담기...
        result.push_back(temp);
    }

	// sqlite3_prepare_v2() 를 리셋시킨다.
    sqlite3_reset(stmt);
    
    // stmt
    sqlite3_finalize(stmt);

    return result;
}

//

bool SQLiteCpp::IsTableExist()
{
    sqlite3_stmt *stmt;
    const char *query = QUERY_TABLE_EXIST.c_str();
    bool result = false;

    sqlite3_prepare_v2(this->db, query, -1, &stmt, NULL);
    if (sqlite3_step(stmt) == SQLITE_ROW)
    {
        result = true;
    }
    sqlite3_reset(stmt);
    sqlite3_finalize(stmt);

    return result;
}

bool SQLiteCpp::CreateTable()
{
    sqlite3_stmt *stmt;
    const char *query = QUERY_TABLE_CREATE.c_str();
    bool result = true;

    sqlite3_prepare_v2(this->db, query, -1, &stmt, NULL);

    // begin
    this->Begin();
    if (sqlite3_step(stmt) != SQLITE_DONE)
    {
        fprintf(stderr, "line %d: %s\n", __LINE__, sqlite3_errmsg(this->db));
        result = false;
    }

    // commit
    this->Commit();

    sqlite3_reset(stmt);
    sqlite3_finalize(stmt);

    return result;
}

bool SQLiteCpp::DropTable()
{
    sqlite3_stmt *stmt;
    const char *query = QUERY_TABLE_DROP.c_str();
    bool result = true;

    sqlite3_prepare_v2(this->db, query, -1, &stmt, NULL);

    // begin
    this->Begin();
    if (sqlite3_step(stmt) != SQLITE_DONE)
    {
        fprintf(stderr, "line %d: %s\n", __LINE__, sqlite3_errmsg(this->db));
        result = false;
    }

    // commit
    this->Commit();

    sqlite3_reset(stmt);
    sqlite3_finalize(stmt);

    return result;
}

bool SQLiteCpp::Begin()
{
    sqlite3_stmt *stmt;
    const char *query = QUERY_DB_BEGIN___.c_str();
    bool result = true;

    sqlite3_prepare_v2(this->db, query, -1, &stmt, NULL);
    if (sqlite3_step(stmt) != SQLITE_DONE)
    {
        fprintf(stderr, "line %d: %s\n", __LINE__, sqlite3_errmsg(this->db));
        result = false;
    }
    sqlite3_reset(stmt);
    sqlite3_finalize(stmt);

    return result;
}

bool SQLiteCpp::Commit()
{
    sqlite3_stmt *stmt;
    const char *query = QUERY_DB_COMMIT__.c_str();
    bool result = true;

    sqlite3_prepare_v2(this->db, query, -1, &stmt, NULL);
    if (sqlite3_step(stmt) != SQLITE_DONE)
    {
        fprintf(stderr, "line %d: %s\n", __LINE__, sqlite3_errmsg(this->db));
        result = false;
    }
    sqlite3_reset(stmt);
    sqlite3_finalize(stmt);

    return result;
}

bool SQLiteCpp::Rollback()
{
    sqlite3_stmt *stmt;
    const char *query = QUERY_DB_ROLLBACK.c_str();
    bool result = true;

    sqlite3_prepare(this->db, query, -1, &stmt, NULL);
    if (sqlite3_step(stmt) != SQLITE_DONE)
    {
        fprintf(stderr, "line %d: %s\n", __LINE__, sqlite3_errmsg(this->db));
        result = false;
    }
    sqlite3_reset(stmt);
    sqlite3_finalize(stmt);

    return result;
}

 

main.cpp

 

#include <iostream>
#include "SQLiteCpp.h"

int main()
{
    SQLiteCpp db;

    if (db._sqlite3_open(DIR))
        std::cout << "1. DB 연결" << std::endl;
    else
        return 0;

    
    std::cout << "2. DB 쿼리 실행" << std::endl;
    std::vector res = db.GetAllRecords();
    for (size_t i = 0; i < res.size(); i++)
    {
        for (size_t j = 0; j < res[0].size(); j++)
        {
            std::cout << res[i][j];
        }

        std::cout << std::endl;
    }

    if (db._sqlite3_close())
        std::cout << "3. DB 연결 해제" << std::endl;

    return 0;
}

④ 실행결과

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.