SQLite, C++, SELECT 예제 첨삭
개요
C/C++ 에서 DB를 사용하는 방법은
TCP/IP 소켓 사용법, 파일 입출력 사용법과 거의 동일한 실행 순서를 가진다.
기반은 리눅스 파일 디스크립터(참고 링크), 윈도우에서는 핸들이라고 한다,를 이용한다.
SQLite 라이브러리는 C언어로 되어 있기 때문에, C++ 코드로 랩핑하여 사용하거나, 그냥 C 코드를 사용해도 무방하다.
가급적 SQLite3 C/C++ Interface 공식문서를 참고하길 권장한다.
참고링크
'프로그래밍 놀이터/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 준비
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입니다.
데이터베이스가 잠긴상태
명령문 실행 완료 되었음을 의미합니다. 이 상태에서 sqlite3_step()를 다시 호출해서는 안 됩니다. 먼저 sqlite3_reset()를 호출하여 초기 상태로 되돌려야 합니다.
실행 중인 SQL 문이 데이터를 반환하는 경우 새 데이터 행을 처리할 준비가 될 때마다 SQLITE_ROW 반환됩니다. sqlite3_step()가 다시 호출되어 다음 데이터 행을 검색합니다.
런타임 오류가 발생했습니다. sqlite3_step()를 다시 호출해서는 안 됩니다.
구체적인 오류 코드(예: SQLITE_INTERRUPT, SQLITE_SCHEMA, SQLITE_CORRUPT 등)
"v2" 인터페이스에서, 보다 구체적인 오류 코드는 sqlite3_step()에 의해 직접 반환됩니다.
this 루틴이 부적절하게 호출되었음을 의미합니다. .
2.3 쿼리 실행 결과에서 한줄의 컬럼 개수 찾기
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;
}
④ 실행결과