sqlite3 에서 virtual table 을 구현한 것 중 기본 제공하는 기능이 csv 파일을 table 처럼 읽는 기능을 제공하는 csvtab이다. virtual table 을 분석하기 아주 좋은 구조라서 정리해 보았다. 해당 코드는 https://github.com/sqlite/sqlite/blob/version-3.38.5/ext/misc/csv.c  에서 볼 수 있다. 

 우선 csvtab 은 write 하는 기능이 없고, 당연히 transaction 관련 기능도 없다. 그래서 간단한 편이다. 앞에서 설명한(https://yiunsr.tistory.com/879) 함수이름에 x 접두어 대신 csvtab 접두어를 사용하고 있다. 

csvtab 을 이용하는 방법은 

CREATE VIRTUAL TABLE temp.csv USING csv(filename=FILENAME);


 이렇게 하거나 

CREATE VIRTUAL TABLE temp.csv2 USING csv(
    filename = "../http.log",
    schema = "CREATE TABLE x(date,ipaddr,url,referrer,userAgent)"
);

이렇게 schema 를 전달 할 수 있다. 또 data 인자롤 통해서 실제 csv 데이터를 넣을 수도 있다. 실제 예제를 찾지는 못했지만 아래처럼 될 것 같다.

CREATE VIRTUAL TABLE temp.csv2 USING csv(
    data = "2021-01-01,1.120.12.12,http://google.com,,chrome\n",
    schema = "CREATE TABLE x(date,ipaddr,url,referrer,userAgent)"
);



CsvReader (https://github.com/sqlite/sqlite/blob/version-3.38.5/ext/misc/csv.c#L74)
* csv 파일에 대한 handler 를 가지고 있고, 해당 파일을 읽은 데이터를 buffer 에 저장해주는 기능을 함
* csv를 파싱할 때 필요한 데이터들을 저장하고 있음. 
* nLine 은 현재 line 을 의미하는데 에러 표시를 위해 사용된다. (컴파일러 이런 것에 몇째 줄에서 오류가 있음. 이런 것 표시할 때 사용하는 line) line 은 csv 와 row 와 다르다. csv 는 column 에 " 를 이용해서 column 안에 Newline(https://ko.wikipedia.org/wiki/%EC%83%88%EC%A4%84_%EB%AC%B8%EC%9E%90) 을 가질 수 있는데 이럴 경우 csv row 는 증가하지 않지만 nLine 은 증가한다. 

CsvTable(https://github.com/sqlite/sqlite/blob/version-3.38.5/ext/misc/csv.c#L307)
* sqlite3_vtab 가 첫 멤버인데, C++의 상속의 역할을 하는 것 같음. using csv 를 사용할 때, 전달되는 paramer 인 filename, data 를 저장하고 csv 파일의 데이터 시작위치(파일 BOM, header 를 제외하고 시작하는 위치), csv 의 column 개수를 저장한다.

static char *csv_read_one_field(CsvReader *p)
* csv데이터를 파싱해서 csv column은 CsvReader.z 에 넣어두고, CsvReader.term 에 column 다음 글자(일반적으로 콤마, newline같은 csv 구분자를 저자하고 있을 것이다. )를 저장한다. 

csvtabCreate
* 내부적으로 csvtabConnect 함수를 호출함. eponymous virtual table 이 되지 않기 위해서 따로 정의해서 사용하고 있음

​csvtabConnect
* CsvTable 메모리 할당
* schema 정의가 안 된 경우 csv 파일을 읽어서 schema 를 자동으로 만들고, 전달된 경우 해당 schema 를 이용해서 sqlite3_declare_vtab 를 호출한다. schema가 정의되어 있지않으면 실제로 csv 파일을 읽거나 생성할 때 전달받은 data 을 받아서 column 개수를 확인 한 후, c1, c2, ... 이런 형태로 column 이름을 정한다. 
* SQLITE_VTAB_DIRECTONLY (trigger 와 view 을 사용할 수 없음)을 설정한다. 

csvtabDisconnect
*
csvtabConnect 에서 할당한 메모리 해제

csvtabBestIndex
* forward full table scan 만을 지원해서 적당히 cost 값을 넣는 것 같다. 어떤 연산이든 상관없이 cost 에 1000000을 넣는다. 

csvtabOpen
* cursor(CsvCursor)에서 사용한 메모리 할당, csv 파일을 open 한다. 

csvtabClose
*
csvtabOpen 에서 사용한 메모리 해제, csv 파일을 닫는다. 

csvtabFilter
* 메모리 할당한 것들에 대해 초기화, 실질적은 동작은 csvtabNext 에서 한다. full table scan 만을 지원하기 때문에 cursor를 csv 파일의 데이터 시작위치로 옮긴다.

csvtabNext
* 하나의 column 을 가져오고 csv 파일의 row의 모든 column 이 csvCursor 에 저장된다. 
* 이 과정에서 cursor의 iRowid 를 값을 증가시키고 값이 없으면 cursor의 iRowid를 -1 로 세팅한다. 
* csv_read_one_field 가 호출되는 과정에서 자동으로 다음 row 를 가져올 수 있도록 설정될 것이다. 

csvtabColumn
* csvCursor 에 저장된 column 데이터를 sqlite3_result_text 를 호출해서 sqlite engine 에 넘겨준다. 
* 실질적으로 데이터가 넘어 가는 부분이다. 

csvtabRowid
* 현재 cursor 의 iRowid 를 리턴한다. 

csvtabEof
* 현재 cursordml iRowid 가 음수이면 eof 이다. 



딱히 뭔가 실질적으로 데이터를 필터링(sql 에서 where 하는 로직때문에 불필요한 row 자체를 전달하지 않는 동작)이 없다. 느낌으로는 sqlite engine 에 다시 뭔가 row 데이터를 검증하는 로직이 있을 것 같다. 뭔가 bloom filter 처럼 아닌 row를 제외해서 csvtabNext 가 알아서 설정하면 효율적으로 동작하고, 아니더라도 sqlite 엔진이 알아서 실제 데이터를 가지고 filtering 해주는 것으로 추정된다.