sqlite3 에서 소스를 수정하지 않고 sqlite3 동작을 수정할 수 있는 방법이 여러가지 있다. 

sqlite3 에 함수를 추가하는 방법(https://www.sqlite.org/c3ref/create_function.html ) , Virtual Table 을 구현하는 방법(https://www.sqlite.org/vtab.html), Virtual FileSystem을 구현하는 방법(https://www.sqlite.org/vfs.html)이있다.(더있는지는 모르겠다.)

어째든 이 방법 중 virtual table 을 구현하는 방법에 대해 공부 중이다. 내 목표는 rust 를 이용해서 vtab을 이용해서 apache arrow parquet 와 연동하고 싶다. (이미 C로 된 것은 https://github.com/cldellow/sqlite-parquet-vtable 이 있다. ). 그래서 vtab 연동하는 인터페이스에 대해 정리해 봤다. ( https://www.sqlite.org/vtab.html )

우선 vtab 을 사용하는 대표적인 경우가 csv 파일을 읽는 csv virtual table(https://www.sqlite.org/csv.html) 이다. 이것을 이용해서 csv 파일을 읽어서 table 처럼 query 를 보낼 수 있다. 

vtab 을 연동하기 위해서는 sqlite3_module  의 struct 를 모듈 생성 함수에 넘겨야 한다. sqlite3_module  구조체가 여러 함수들의 callback 함수로 구성되어 있기 때문에 이 callback 함수를 구현해야 한다. 필수 구현함수가 아닌 경우 null 을 넣는 경우수도 있다. 
함수 원형은 아래와 같이 생겼다. 

더보기

 

struct sqlite3_module {
  int iVersion;
  int (*xCreate)(sqlite3*, void *pAux,
               int argc, char *const*argv,
               sqlite3_vtab **ppVTab,
               char **pzErr);
  int (*xConnect)(sqlite3*, void *pAux,
               int argc, char *const*argv,
               sqlite3_vtab **ppVTab,
               char **pzErr);
  int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*);
  int (*xDisconnect)(sqlite3_vtab *pVTab);
  int (*xDestroy)(sqlite3_vtab *pVTab);
  int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor);
  int (*xClose)(sqlite3_vtab_cursor*);
  int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
                int argc, sqlite3_value **argv);
  int (*xNext)(sqlite3_vtab_cursor*);
  int (*xEof)(sqlite3_vtab_cursor*);
  int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int);
  int (*xRowid)(sqlite3_vtab_cursor*, sqlite_int64 *pRowid);
  int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite_int64 *);
  int (*xBegin)(sqlite3_vtab *pVTab);
  int (*xSync)(sqlite3_vtab *pVTab);
  int (*xCommit)(sqlite3_vtab *pVTab);
  int (*xRollback)(sqlite3_vtab *pVTab);
  int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName,
                     void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
                     void **ppArg);
  int (*Rename)(sqlite3_vtab *pVtab, const char *zNew);
  /* The methods above are in version 1 of the sqlite_module object. Those 
  ** below are for version 2 and greater. */
  int (*xSavepoint)(sqlite3_vtab *pVTab, int);
  int (*xRelease)(sqlite3_vtab *pVTab, int);
  int (*xRollbackTo)(sqlite3_vtab *pVTab, int);
  /* The methods above are in versions 1 and 2 of the sqlite_module object.
  ** Those below are for version 3 and greater. */
  int (*xShadowName)(const char*);
};


xCreate : int (*xCreate)(sqlite3 *db, void *pAux, int argc, char *const*argv, sqlite3_vtab **ppVTab, char **pzErr);
xConnect :
int (*xConnect)(sqlite3*, void *pAux, int argc, char *const*argv, sqlite3_vtab **ppVTab, char **pzErr);
* xCreate  는 생성함수로 vtab 이 생성될 때 호출된다. SQL 구문  CREATE VIRTUAL TABLE 테이블명 ....     형식이다. 
xCreate와 xConnect 는 유사하다. xCrete 는 생성될 때 이용되고, xConnect 는 생성된 가상테이블에 대해 불러올 때 사용된다. xCrete 와 xConnect 를 동일한 callback 함수(동일한 함수포인터)를 넣을 수도 있는데, 이렇게 하면 Eponymous virtual tables(CREATE VIRTUAL TABLE 문 없이 미리 로딩된 경우) 이 된다. xCreate 가 null pointer 이면 eponymous-only virtual table(CREATE VIRTUAL TABLE 문을 사용할 수 없고, table-valued functions 전용, table-valued functions 은 table 을 return 하는 sql 함수로 generate_series 함수 같이 일반 함수인데, table row 를 리턴해서 테이블 처럼 SELECT value FROM generate_series(5,50); 으로 사용 가능 )
csv virtual table 경우 xCrete 내부적으로 xConnect 를 호출하고 있다. 
* 꼭 이들 함수 안에서 sqlite3_declare_vtab 를 호출해서 가상테이블의 column 과 datatype 을 알려야 한다. 일반 database 의 table 을 create 하듯이 sql 쿼리를 전달해야한다. 필수 구현 사항이다.
* 또 sqlite3_vtab_config 를 호출해서 SQLITE_VTAB_CONSTRAINT_SUPPORT , SQLITE_VTAB_INNOCUOUS, SQLITE_VTAB_DIRECTONLY  중 하나를 선택해서 가상 테이블에 대한 환경설정을 할 수도 있다. 이것은 옵션이라서 호출 안해도 상관 없어 보인다. SQLITE_VTAB_INNOCUOUS(trigger 와 view를 악의적으로 사용되더라도 피해를 줄 수 없음),   SQLITE_VTAB_DIRECTONLY(trigger 와 view를 사용금지), SQLITE_VTAB_CONSTRAINT_SUPPORT(2번째 argument 도 활용해서 좀 복잡하다. )

xBestIndex  (int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*);)
* 연산에 대해 처리 cost 를 계산해서 효율적인 접근 방법을 계산할 때 이용한다. sqlite3_prepare 또는 유사한 기능(아마 쿼리를 직접 호출하는 경우를 말하는 것 같다.)을 이용할 때, 이 callback 함수가 호출되며, 여러번 호출될 수 있다. 그러면서 최적의 속도를 얻을 수 있는 방법을 계산한다. 이 함수가 구현의 핵심부분이라 더 자세히 찾아볼 필요가 있다. 

xDisconnect : int (*xDisconnect)(sqlite3_vtab *pVTab);

xDestroy
: int (*xDestroy)(sqlite3_vtab *pVTab);
* xDisconnect 는 삭제가 아닌 연결만을 해제합니다. xDisconnect 는 DB연결이 close 될 때 호출되고, xDestroy 는  DROP TABLE 구문이 실행될 때, 호출된다. csv virtual table 경우 둘 다 동일한 callback point 이다. 

xOpen : int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor);
xClose
: int (*xClose)(sqlite3_vtab_cursor*);
* xOpen는 virtual table 에 접근하는(read / write) 하는 cursor를 생성한다. xClose는 이 cursor 를 close 한다. xOpen 은 모든 virtual table 에 필수적으로 필요한 callback 이다. 이렇게 해서 생성된 cursor는 cursor의 위치를 지정하거(file seek의 의미인듯) read 하기 전에 xFilter 를 호출한다. 

xEof : int (*xEof)(sqlite3_vtab_cursor*);
* 현재 cursor 가 유효한 레코드를 가리키고 false, 아니면 true 를 리턴한다. xEof 는 xNext 나 xFilter 호출후에 호출된다.

xFilter : int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr, int argc, sqlite3_value **argv);
* xOpen에 의해 열린 cursor 에 대해 검색을 함. 필터 기준에 만족하는 레코드가 있으면 커서를 해당 레코드에 위치시킨다. 검색 조건은 argc, argv에 의해 전달되는데, 이 값은 xBestIndex  함수가 호출될 때, 설정된 값으로 전달되는 것 같다. (정확히 어떻게 전달되는지 확인필요). 

xNext : int (*xNext)(sqlite3_vtab_cursor*);
* xFilter 에 의해 cursor 가 세팅된 이후, 값이 불리고 나서 다음 record가 호출될 때, xNext 가 불러지는 것 같다. 

xColumn : int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int N);
* 현재 행의 N번째 열에 대한 값을 찾기 위해 이 메서드를 호출합니다

xRowid : int (*xRowid)(sqlite3_vtab_cursor *pCur, sqlite_int64 *pRowid);
* 현재 cursor에 대한 rowID 를 pRowid 로 전달한다. 

xUpdate : int (*xUpdate)(sqlite3_vtab *pVTab, int argc, sqlite3_value **argv, sqlite_int64 *pRowid);
* 가상테이블에 대해 delete, insert, update 쿼리를 보낼 때, 호출된다. 가상테이블에 대한 수정을 지원하지 않는 경우 구현하지 않아도 된다. 

xFindFunction : int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName,
  void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg);
* sqlite3_prepare 호출될 때, 가상 테이블 구현에 대해 함수를 overload 할 기회를 제공한다. 여기서는 virtual table 에 대해서만 생각했지만, sqlite 에서 모듈을 이용해서 SQL 함수를 구현할 수 있다. 이렇게 구현한 SQL 함수에 대해 다시 재 정의가 가능한 것 같다. 

xBegin : int (*xBegin)(sqlite3_vtab *pVTab);
xSync : int (*xSync)(sqlite3_vtab *pVTab);
xCommit : int (*xCommit)(sqlite3_vtab *pVTab);
xRollback : int (*xRollback)(sqlite3_vtab *pVTab);
* 가상 테이블에 대해 transaction을 구현할 때, callback 이 호출됨

xSavepoint : int (*xSavepoint)(sqlite3_vtab *pVtab, int);
xRelease : int (*xRelease)(sqlite3_vtab *pVtab, int);
xRollbackTo : int (*xRollbackTo)(sqlite3_vtab *pVtab, int);
* transaction 에서 더 나가 savepoint 기능을 구현할 때 필요

xRename : int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);
* alter table 을 구현할 때 필요