SQLite 프로젝트는 2000년에 시작된 이래 현재까지 소스가 오픈되어 있습니다.

누구나 소스를 살펴볼 수 있고, 빌드하여 사용할 수 있다.


버전이 올라가는 것만 봐도 프로젝트가 꾸준히 진행되는 것을 엿볼 수 있습니다.

불과 한 달 전(2015. 5. 20)에도 3.8.10.2 버전이 릴리스되었습니다.


이러한 프로젝트를 15년이 넘게 '무료'로 운영하긴 힘들겁니다.

생업이란 것이 있고 가정도 있을테니 무일푼으로 개발하긴 쉽지 않죠.

SQLite 개발자는 현실적인 삶을 어떻게 유지하나 궁금했었는데 수익모델이 있었군요.



물론, SQLite 대부분의 기능은 무료입니다.

상용 플랫폼에 탑재하여 사용할 수도 있습니다.

프로그램 자체가 가볍고 정교하게 만들어졌는데 무료라니 사용을 마다할 이유가 없죠.


하지만, SQLite Encryption Extension(SEE) 부분은 오래 전부터 상용화를 위해 비워두었더군요.

DB의 기본적인 기능은 무료 기조를 유지하되,

고객정보노출에 대해 민감한 업체에는 암호화 부분을 판매하는 것이지요.

SQLite에는 아래처럼 SEE에 대한 홈페이지도 마련되어 판매를 촉진하고 있습니다.



위의 홈을 통해 SEE의 소스를 볼 수 있습니다.

다만, License를 구매한 사람만 볼 수 있지요.

SEE License 가격은 $2,000(대략 220만원)입니다.

http://www.hwaci.com/sw/sqlite/see.html?

SQLite를 구매할 정도로 넉넉한 살림살이가 아니기 때문에 라이센스 구매는 포기합니다.


대신 외부로 노출되어 있는 함수를 살펴보겠습니다.

구미가 당기시나요?


첫번째 함수는 DB를 암호화하거나 기존 암호를 변경할 때 사용합니다.


   int sqlite3_rekey_v2(
      sqlite *db,                    /* Database to be rekeyed */
      const char *zDbName,           /* Which ATTACHed database to rekey */
      const void *pKey, int nKey     /* The new key */
   );


1. 첫번째 인자는 db 커넥션을 의미합니다.

    sqlite3_open()으로 생성한 db 커넥션을 입력하면 됩니다.

2. zDBName은 Attach한 DB의 이름을 의미합니다.

    - 만약 Attach한 DB가 있으면,

      기존 db는 "main"(기존, NULL과 같음)이 되는 것이고,

      임시 db는 "temp"가 되겠지요.

      그 외에는 사용자가 직접 수행하는 ATTACH 명령어로 이름을 지정할 수 있습니다.

    - Attach한 DB가 없으면 그냥 NULL을 넣으면 되죠.

      (ATTACH에 대한 설명은 언젠가 기회가 되면 하겠습니다.)

3. 세번째에는 새로 설정할 키값을 넣습니다.

4. 네번째는 세번째 인자로 넣은 키값의 길이를 적으면 됩니다.

    strlen(pKey)하면 됩니다.


아래 순서로 사용하면 되겠군요~

sqlite3_open() → sqlite3_rekey_v2() → ... → sqlite3_close()


   int sqlite3_key_v2(
      sqlite3 *db,         /* The connection from sqlite3_open() */
      const char *zDbName, /* Which ATTACHed database to key */
      const void *pKey,    /* The key */
      int nKey             /* Number of bytes in the key */
   );


위의 함수가 암호가 걸린 db 파일을 복호화하는 함수입니다.

sqlite3_open() 함수로 db 파일을 연 직후에 수행해야 하지요.

암호가 걸린 파일은,

sqlite3_key_v2()로 복호화에 성공해야 db에 정상적으로 접근할 수 있습니다.


1. 위의 함수에서 db는 db 커넥션을 말합니다.

2. zDBName은 위에서 설명한 대로 Attach한 DB의 이름을 채워넣어줘야 합니다.

    NULL을 입력하면, 내부적으로 "main"에 해당하는 값이 설정되지요.

3. 세번째에는 암호를 풀 키값을 넣습니다.

4. 네번째는 세번째 인자로 넣은 키값의 길이를 적으면 됩니다.


sqlite3_open() → sqlite3_key_v2() → ... → sqlite3_close()

위와 같은 순서로 사용하게 됩니다.


다만 DB에 암호가 걸려있지 않다면 sqlite3_key_v2()를 사용할 필요는 없습니다.

기존에 걸린 암호를 없애려면,

sqlite3_rekey_v2(db, zDbName, NULL, 0);

위와 같이 설정하면 됩니다.


이처럼 새롭게 생성된 sqlite3_rekey_v2() 혹은 sqlite3_key_v2() 대신

PRAGRMA로 key, rekey 기능을 수행할 수 있습니다.


DB를 암호화하려면 sqlite3 shell에서 아래 명령어만 수행하면 됩니다.

sqlite> PRAGMA rekey="your_new_key";

암호를 풀려면 아래 명령어를 수행합니다.

sqlite> PRAGMA key="your_encryption_key";


sqlite> .rekey OLD NEW NEW

위의 명령어로 설정된 암호를 변경할 수 있습니다.

첫번째 인자가 예전키값이고, NEW/NEW는 신규키값입니다.

신규 키를 두 개 연달아 입력하는 이유는 비번 설정시 확인하는 절차를 주기 위해서입니다.


언제나 자본과는 무관할 줄 알았던 SQLite의 행보를 보니,

(사실 자본주의 시대에 자본과 무관하긴 힘들죠.

개발자로서 수익원을 창출한 SQLite에 갈채를 보냅니다.)

다음 수가 기대가 되기 시작합니다..


끝_


* References

http://www.hwaci.com/sw/sqlite/see.html?

http://www.sqlite.org/see/doc/trunk/www/readme.wiki

한밤 중 1시 51분,

잘까 말까 잠시 망설이다가,

DB에 대한 포스팅을 마무리하기 위해 세수를 하고 왔습니다.


이 포스팅에는 대단한 스키마가 나오는 것도 아니고,

어마무시한 쿼리문도 나오지 않습니다.


그저 비몽사몽 간에,

앱단에서 사용할만한 함수 몇가지를 공유하고자 합니다.


타이젠에서 공식적으로 지원하는 DB는 SQLite3입니다.

(참고, "Tizen 플랫폼 DB 엿보기-", http://storycompiler.tistory.com/25)

오래전부터 SQLite였고 그 기조는 당분간 바뀌지 않을겁니다.

따라서 SQLite를 다른 DB로 포팅할 일도 없겠군요.


그렇지만, 순전히 앱사이드에서 sqlite3를 덕지덕지 소스에 붙여 놓고 싶지도 않습니다.

sqlite3_open() / sqlite3_close()를 매번 수행하며,

그 때마다 온갖 예외처리를 하다보면,

분명 비슷한 루틴이 반복될 것이고,

그러는 와중에 버그라는 친숙한 녀석을 만나게 될겁니다.


따라서 sqlite3를 앱 내부 함수에 캡슐로 감춰서

조금이라도 노출을 막고,

조금이라도 쉽게 사용하고자 합니다.


static struct {
    sqlite3 *db;
} db_info = {
    .db = NULL,
};


우선, sqlite3_open()으로 나오는 db 핸들은 static 전역 변수로 선언해볼까요?

전역 변수는 매우 위험하여 가급적이면 사용하진 않지만,

- 개발하고자 하는 앱은 오직 한 프로세스에서만 사용하고,

- 단일스레드로 동작하며,

- reentrant 따위는 일어날리도 없기에,

단일 파일에서만 접근할 수 있는 static 전역 변수로 선언하곤 합니다.


이로써 모든 함수의 첫번째 인자(sqlite3 *db)는 생략할 수 있게 된 셈이지요.

덕분에 API 사용이 간단해졌습니다.

하지만, 이러한 제한적인 상황 외에 전역변수를 남용한다면,

숨겨진 버그를 중요한 순간마다 튀어나와 정신건강에 치명적인 독이 되고 맙니다. :)

특히, 라이브러리에서 전역변수는 고민에 고민을 거듭하여 제거하는게 좋겠지요.


db_open()은 이전 포스팅에서 설명한 바 있습니다.

Tizen 플랫폼에서 앱의 데이터 저장공간을 app_get_data_path()로 얻어와 DB 파일을 저장합니다.

(참고, "Tizen 앱 DB는 언제 어디에 초기화할까", http://storycompiler.tistory.com/29)


HAPI appl_error_e db_open(void)
{
    char *path = NULL;
    char db_file[FILE_LEN] = {0, };
    int ret = SQLITE_OK;

    path = app_get_data_path();
    retv_if(!path, APPL_ERROR_FAIL);
   
    snprintf(db_file, sizeof(db_file), "%s/%s", path, APP_DB_FILE);

    ret = sqlite3_open(db_file, &db_info.db);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        free(path);
        return APPL_ERROR_FAIL;
    }

    free(path);
    return APPL_ERROR_NONE;
}

HAPI void db_close(void)
{
    if (!db_info.db) {
        _D("DB is already NULL");
        return;
    }

    sqlite3_close(db_info.db);
    db_info.db = NULL;
}


db_open()과 db_close()는 db 핸들을 전역으로 뽑아놨기 때문에 함수 패러미터도 없습니다.

따라서 원하는 시점에 인자에 대한 고민없이 open / close 하면 됩니다.

편하군요~


HAPI sqlite3_stmt *db_prepare(const char *query)
{
    sqlite3_stmt *stmt = NULL;
    int ret = SQLITE_OK;

    retv_if(!query, NULL);

    ret = sqlite3_prepare_v2(db_info.db, query, strlen(query), &stmt, NULL);
    if (SQLITE_OK != ret) {
        _E("%s, %s", query, sqlite3_errmsg(db_info.db));
        return NULL;
    }

    return stmt;
}
HAPI appl_error_e db_next(sqlite3_stmt *stmt)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);

    ret = sqlite3_step(stmt);
    switch (ret) {
    case SQLITE_ROW:
        return APPL_ERROR_NONE;
    case SQLITE_DONE:
        return APPL_ERROR_NO_DATA;
    default:
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}
HAPI appl_error_e db_reset(sqlite3_stmt *stmt)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_INVALID_PARAMETER);

    ret = sqlite3_reset(stmt);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    sqlite3_clear_bindings(stmt);

    return APPL_ERROR_NONE;
}


db_prepare(), db_next(), db_reset()은 sqlite3_prepare_v2(), sqlite3_next(), sqlite3_reset() 기능을 수행하기 위한 함수입니다.

우선, db_prepare()에서 패러미터로 전달받은 쿼리문을 파싱하여 sqlite3 *stmt를 리턴하죠.

stmt는 db_next()에 인자로 들어가서 실행됩니다.

db_reset()에서는 stmt를 재사용하기 위해 bind된 인자가 있으면 clear 합니다.

반드시 clear를 하고 다시 bind를 해야합니다.

어렴풋 reset을 제대로 하지 않아서 삽질을 했던 기억이 떠오르네요.

여기서 bind는 이어서 설명하는 db_bind_xxxx()함수와 연결됩니다.


HAPI appl_error_e db_bind_bool(sqlite3_stmt *stmt, int idx, bool value)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);

    ret = sqlite3_bind_int(stmt, idx, (int) value);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}
HAPI appl_error_e db_bind_int(sqlite3_stmt *stmt, int idx, int value) {
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);

    ret = sqlite3_bind_int(stmt, idx, value);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}
HAPI appl_error_e db_bind_double(sqlite3_stmt *stmt, int idx, double value)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);

    ret = sqlite3_bind_double(stmt, idx, value);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}
HAPI appl_error_e db_bind_str(sqlite3_stmt *stmt, int idx, const char *str)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_FAIL);
    retv_if(!str, APPL_ERROR_FAIL);

    ret = sqlite3_bind_text(stmt, idx, str, strlen(str), SQLITE_TRANSIENT);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}


바인드는 db_prepare()에서 파싱한 쿼리문 중에 '?'로 처리한 부분에 치환되어 들어갑니다.

문자열 쿼리문을 더 빨리 처리하기 위해,

확정된 부분은 db_prepare()로 먼저 파싱하고,

확정할 수 없는 부분은 sqlite3_bind_xxxx()로 차후에 입력하게 됩니다.


예를 들어,

query = "select sequence from bookmarks where parent=? order by sequence desc";

(참고, git://review.tizen.org/apps/web/browser, src/database/browser-bookmark-db.cpp)

위와 같은 쿼리문의 where 절에 '?'로 처리된 부분은,

sqlite3_bind_int로 차후에 채워넣게 됩니다.


단, 항상 헛갈리는 부분인데 bind에 들어가는 index는 0이 아닌 1로 시작합니다. 응?

위의 예에서 parent의 '?'에 바인드하기 위해서는 '0'이 아니라 '1'을 index로 넣어야 합니다.

오래 전에 SQLite 서적을 보며 1부터 시작하는 납득할 만한 이유를 찾았던 것으로 기억하는데,

더 이상 기억이 나지 않는 것으로 보아 충분히 납득하지 못했었나 봅니다.


HAPI bool db_get_bool(sqlite3_stmt *stmt, int index)
{
    retv_if(!stmt, false);
    return (bool) sqlite3_column_int(stmt, index);
}
HAPI int db_get_int(sqlite3_stmt *stmt, int index)
{
    retv_if(!stmt, 0);
    return sqlite3_column_int(stmt, index);
}
HAPI int db_get_double(sqlite3_stmt *stmt, int index)
{
    retv_if(!stmt, 0);
    return sqlite3_column_double(stmt, index);
}
HAPI const char *db_get_str(sqlite3_stmt *stmt, int index)
{
    retv_if(!stmt, NULL);
    return (const char *) sqlite3_column_text(stmt, index);
}


위의 함수군은 select 쿼리의 결과로 나온 값들을 얻기 위해 사용합니다.

index는 0부터 차례대로 카운팅됩니다.

bind처럼 1부터 시작하지 않고 0부터 시작하니 헛갈리지 마세요.


HAPI appl_error_e db_finalize(sqlite3_stmt *stmt)
{
    int ret = SQLITE_OK;

    retv_if(!stmt, APPL_ERROR_INVALID_PARAMETER);

    ret = sqlite3_finalize(stmt);
    if (SQLITE_OK != ret) {
        _E("%s", sqlite3_errmsg(db_info.db));
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}


stmt를 더 이상 사용할 필요가 없다면 db_finalize를 해야합니다.

이로써 db_prepare()에서 생성된 stmt의 라이프사이클이 드디어 종료됩니다.

db_prepare()로 핸들을 생성하였으면,

반드시 db_finalize()까지 진행해주세요.


HAPI appl_error_e db_exec(const char *query)
{
    sqlite3_stmt *stmt = NULL;

    retv_if(!query, APPL_ERROR_INVALID_PARAMETER);

    stmt = db_prepare(query);
    retv_if(!stmt, APPL_ERROR_FAIL);

    goto_if(APPL_ERROR_FAIL == db_next(stmt), ERROR);
    goto_if(APPL_ERROR_FAIL == db_finalize(stmt), ERROR);

    return APPL_ERROR_NONE;

ERROR:
    if (stmt) db_finalize(stmt);
    return APPL_ERROR_FAIL;
}


db_exec()는 create / drop / insert / update / delete 처럼,

유의미한 실행결과를 얻을 필요가 없는 쿼리문에 사용합니다.


쿼리문과 db_exec()만 사용하면 얼마든지 원하는 쿼리문을 수행할 수 있습니다.

하지만, 너무나 쉽게 사용할 수 있다보니 해킹의 위험에 노출될 수도 있죠.

개발자가 최초에 쿼리문을 하나 만들어 sqlite3_exec()에 넣었다고 해보죠.

해커가 쿼리문의 "%s" 같은 부분을 조작하여 다중 쿼리문으로 바꿔치기하면,

력된 다중 쿼리문이 sqlite3_exec()에 의해 모두 실행됩니다.

난리나게 되는 거죠.


따라서 언제나 오직 하나의 쿼리문만 실행할 수 있도록,

db_exec() 내부에서 prepare, next, finalize를 수행하도록 변경하였습니다.

next는 하나의 쿼리문만 수행하도록 설계가 되어 있답니다.


HAPI appl_error_e db_begin_transaction(void)
{
    int ret = SQLITE_BUSY;

    while (1) {
        ret = sqlite3_exec(db_info.db, "BEGIN IMMEDIATE TRANSACTION", NULL, NULL, NULL);
        if (SQLITE_BUSY != ret) {
            break;
        }
        /* FIXME : we have to fix this sleep */
        sleep(1);
    }

    if (SQLITE_OK != ret) {
        _E("sqlite3_exec() Failed(%d)", ret);
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}



HAPI appl_error_e db_end_transaction(void)
{
    int ret = SQLITE_OK;

    while (1) {
        ret = sqlite3_exec(db_info.db, "COMMIT TRANSACTION", NULL, NULL, NULL);
        if (SQLITE_BUSY != ret) {
            break;
        }
        /* FIXME : we have to fix this sleep */
        sleep(1);
    }

    if (SQLITE_OK != ret) {
        _E("sqlite3_exec() Failed(%d)", ret);
        return APPL_ERROR_FAIL;
    }

    return APPL_ERROR_NONE;
}


트랜잭션이 필요할 수도 있겠네요.

BEGIN TRANSACTION - COMMIT TRANSACTION 사이에 벌어지는 디비 루틴은 한 번에 처리됩니다.


다만, 상기 함수에서 트랜잭션 쿼리를 수행할 때,

SQLITE_BUSY가 나는 경우 sleep()을 걸어놓았습니다.

sleep()은 프로세스 전체를 멈추게 할 수도 있으니 신중하게 사용해야 합니다.

프로세스의 디자인에 따라 이 부분은 각자의 mainloop()에 사용할 수 있는 timer 등으로 치환할 수도 있습니다.


이제는 정말 취침에 들어가야겠네요.

집중력이 바닥났습니다.


끝_


* 2015. 6. 2 정신차리고 일어나서 한밤중에 멋대로 휘갈긴 문장을 수정.


  1. developer 2016.06.29 16:12

    애정이 느껴지네요 잘보고 갑니다 ㅎㅎ

    • 안녕하세요, 개발자님. 애정은 넘치죠. 하하하. 제가 지금은 러시아에 나와 있어서 잠시 타이젠과 멀어져 있긴 한데... 언제나 애정만큼은 충만해있어요.

간단한 앱을 하나 짜더라도

사용자로그를 관리하기 위해서는 DB가 필요하죠.


하지만, 플랫폼마다 지원하는 DBMS가 달라서,

낯선 DBMS 환경에 적응해야하는 경우도 있습니다.


다행스럽게도 타이젠에서는 모바일이나 웨어러블 같은 임베디드 환경에서,

가장 널리 사용되고 있는 SQLite를 지원하고 있습니다.


안녕하세요, Tizen 개발자 윤진입니다.


타이젠 2.3에서 지원하고 있는 API 레퍼런스를 살펴보면,

API Reference > Native Application > Native API Reference > Base > sqlite

위와 같이 SQLite 항목을 찾을 수 있습니다.



SQLite 항목을 살펴보면,

SQLite는 가벼운 sql 데이터베이스로 라이브러리 형태로 제공된다고 언급되어 있습니다.

플랫폼에 탑재된 버전은 3.7.13(2015. 5. 28 현재 3. 8. 10. 2가 최신버전)이고,

Documentation은 SQLite 공식사이트(http://www.sqlite.org/docs.html)에 방문하여 보아야 합니다.

SQLite는 시중에 좋은 책도 많이 나와있고,

구글링으로도 깊이 있는 정보를 찾을 수 있기 때문에 API 사용법에 대한 언급은 하지 않겠습니다.


타이젠 플랫폼 소스를 살펴보면 몇가지 흥미로운 사실을 발견할 수 있습니다.


- sqlite3_open()을 직접 호출해주는 라이브러리가 몇개 있습니다.

  libslp-memo, libslp-alaram는 라이브러리 이름으로 파악해보건대,

  위의 라이브러리를 사용하는 앱에서만 DB 정보를 독점하는 것으로 보입니다.

  다른 프로세스에 앱 DB에 대한 정보를 건네줄 필요가 없기 때문에,

  앱에서 사용하는 라이브러리내에 직접 sqlite3를 사용했겠지요.


- 앱단에서는 sqlite3_open() 대신 db_util_open()이라는 유틸리티 함수를 사용하고 있네요.

  db_util_open() 함수 내부에서 sqlite3_open()을 하고, busy handler와 journal mode를 설정하고 있습니다.


/**
 * @brief invoke sqlite3_open with platform common configuration
 * @details register busy handler, create localized collation
 * @param [in] database file name (UTF-8)
 * @param [out] SQLite database handle
 * @param [in] option value
 * @return sqlite3 function return value will be returned
 * @see db_util_open_with_options()
 * @see db_util_close()
 *
 */
EXPORT_API int db_util_open(const char *pszFilePath, sqlite3 **ppDB,
                        int nOption);

위의 함수는 "git://review.tizen.org/framework/appfw/libslp-db-util"의 "include/util-func.h"에서 볼 수 있습니다.

첫번째 인자로 DB 파일이름을 넣어주면,

두번째 인자로 sqlite handle을 넘겨줍니다.


static int __db_util_open(sqlite3 *ppDB)
{
   /* ...생략... */
    /* Register Busy handler */
    rc = sqlite3_busy_handler(ppDB, __db_util_busyhandler, NULL);
    if (SQLITE_OK != rc) {
        DB_UTIL_TRACE_WARNING("Fail to register busy handler\n");
        sqlite3_close(ppDB);
        return rc;
    }

    /* Code to change default journal mode of sqlite3 is enabled so this option is disabled */
    /* Enable persist journal mode */
    rc = sqlite3_exec(ppDB, "PRAGMA journal_mode = PERSIST",
            NULL, NULL, &pszErrorMsg);
    if (SQLITE_OK != rc) {
        DB_UTIL_TRACE_WARNING("Fail to change journal mode: %d, %d, %s, %s\n",
                                sqlite3_errcode(ppDB),
                                sqlite3_extended_errcode(ppDB),
                                pszErrorMsg,
                                sqlite3_errmsg(ppDB));
        sqlite3_free(pszErrorMsg);
        sqlite3_close(ppDB);
   /* ...생략... */ }


위의 코드에서 busy handler를 등록하여,

DB가 EXCLUSIVE 등의 이유로 busy인 경우 retry하도록 설정합니다.


그리고 저널모드로 Persist를 지정해줍니다.

Persist는 롤백 루틴에 사용하는 파일을 일정한 크기로 유지하여,

파일 create & destroy에 따른 비용을 줄이고,

read & write를 최소한의 비용으로 할 수 있게,

디스크 공간은 버리고 성능을 택하는 저널방법입니다.

적어도 이 API만 보면, 타이젠은 성능 최우선의 정책을 취하는 것으로 보입니다.


busy handler에서는 usleep()을 직접 썼습니다.

usleep()은 프로세스 자체를 멈춰버리므로,

busy 상황에서 db_util_open()을 사용하여 강제로 앱프로세스를 멈추게 합니다.


static int __db_util_busyhandler(void *pData, int count)
{
    if(5 - count > 0) {
        DB_UTIL_TRACE_DEBUG("Busy Handler Called! : PID(%d) / CNT(%d)\n", getpid(), count+1);
        usleep((count+1)*100000);
        return 1;
    } else {
        DB_UTIL_TRACE_DEBUG("Busy Handler will be returned SQLITE_BUSY error : PID(%d) \n", getpid());
        return 0;
    }
}


db_util_open()은 framework이나 app단 모두에서 고루 사용하고 있습니다.

framework/api/favorites

framework/pim/calendar-service

framework/pim/contact-service

framework/messaging/email-service

framework/appfw/ail

framework/appfw/slp-pkgmgr

framework/appfw/alarm-manager

apps/home/menu-screen

apps/home/notification

apps/web/browser

등 상당수의 framework와 app에서 사용하고 있습니다.


DB를 open하는 방식으로도 타이젠 플랫폼의 정책을 엿볼 수 있습니다.

플랫폼 버전이 업그레이드될 때마다,

세부구현방식은 바뀔 수 있지만,

플랫폼 정책은 바꾸기 힘들겠죠.

경량화와 빠른 속도를 특장점으로 내세우는 타이젠은,

어쩌면, ivi나 IoT에 적합한 플랫폼일지도 모른다는 생각이 듭니다.


이제 너무 졸려 더 이상 단어를 쓸 여력이 없는 새벽 1시 54분에 마칩니다.


끝_




+ Recent posts