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 해주는 것으로 추정된다.