SQLite Zipfile 모듈
SQLite Zipfile 모듈 (The SQLite Zipfile Module)
SQLite에서 ZIP 아카이브를 읽고 쓸 수 있게 해주는 zipfile 모듈(테이블 값 함수, 가상 테이블, 집계 함수)에 대해 설명하는 문서예요.
본문
1. 개요 (Overview)
zipfile 모듈은 단순한 ZIP 아카이브에 대한 읽기/쓰기 접근을 제공해요. 현재 구현에는 다음과 같은 제약이 있어요:
- 암호화를 지원하지 않아요.
- 여러 파일에 걸친 ZIP 아카이브(spanning)를 지원하지 않아요.
- zip64 확장을 지원하지 않아요.
- 지원하는 유일한 압축 알고리즘은 "deflate"예요.
이 제약 중 일부 또는 전부가 앞으로 제거될 수 있어요.
2. Zipfile 얻기와 컴파일하기 (Obtaining and Compiling Zipfile)
zipfile 모듈의 코드는 메인 SQLite 소스 트리의 ext/misc/zipfile.c 파일에 있어요. 다음과 같은 명령으로 SQLite 로더블 확장(loadable extension)으로 컴파일할 수 있어요:
gcc -g -fPIC -shared zipfile.c -o zipfile.so
또는 zipfile.c 파일을 응용 프로그램에 컴파일할 수도 있어요. 이 경우 다음 함수를 호출해 각 새 데이터베이스 연결에 확장을 등록해야 해요:
int sqlite3_zipfile_init(sqlite3 *db, void*, void*);
첫 번째 인자는 확장을 등록할 데이터베이스 핸들이어야 해요. 두 번째와 세 번째 인자는 둘 다 0을 전달해야 해요.
Zipfile은 명령줄 셸의 대부분의 빌드에 포함돼요.
3. Zipfile 사용하기 (Using Zipfile)
zipfile 모듈은 zip 파일 아카이브에 접근하고, 갱신하고, 생성하기 위한 세 가지 유사한 인터페이스를 제공해요:
- 테이블 값 함수(table-valued function) - 파일 시스템이나 메모리의 기존 아카이브에 읽기 전용 접근을 제공해요.
- 가상 테이블(virtual table) - 파일 시스템에 저장된 아카이브에 읽기와 쓰기 접근을 제공해요.
- SQL 집계 함수(aggregate function) - 메모리에서 새 아카이브를 만드는 데 사용할 수 있어요.
3.1. 테이블 값 함수 (읽기 전용 접근)
기존 zip 아카이브를 읽기 위해 Zipfile 모듈은 단일 인자를 받는 테이블 값 함수를 제공해요. 인자가 텍스트 값이면 파일 시스템에서 읽을 zip 아카이브의 경로예요. 또는 인자가 SQL blob이면 zip 아카이브 데이터 자체예요.
예를 들어 현재 디렉터리의 zip 아카이브 "test.zip"의 내용을 검사하려면:
SELECT * FROM zipfile('test.zip');
또는 SQLite 셸 도구에서 (readfile() 함수는 파일 시스템에서 파일 내용을 읽어 blob으로 반환해요):
SELECT * FROM zipfile( readfile('test.zip') );
테이블 값 함수는 zip 아카이브의 각 레코드(파일, 디렉터리 또는 심볼릭 링크)마다 한 행을 반환해요. 각 행은 다음 열을 가져요:
| 열 이름 | 내용 |
|---|---|
| name | zip 파일 레코드의 파일 이름/경로예요. |
| mode | zip 파일 레코드에 대해 stat(2)가 반환하는 UNIX 모드(정수)예요. 레코드의 유형(파일, 디렉터리 또는 심볼릭 링크)과 관련된 사용자/그룹/모두 권한을 식별해요. |
| mtime | UNIX epoch 이후 초 단위의 UTC 타임스탬프(정수)예요. |
| sz | 관련 데이터의 압축 해제 후 크기(바이트, 정수)예요. |
| rawdata | zip 파일 항목과 관련된 원시(아마 압축된) 데이터(blob)예요. |
| data | 레코드의 압축 방법이 0 또는 8이면(아래 참고) zip 파일 항목과 관련된 압축 해제된 데이터예요. 압축 방법이 0 또는 8이 아니면 이 열은 NULL 값을 담아요. |
| method | 데이터를 압축하는 데 사용된 압축 방법(정수)이에요. 값 0은 데이터가 압축 없이 zip 아카이브에 저장됨을 나타내요. 8은 원시 deflate 알고리즘을 의미해요. |
3.2. 가상 테이블 인터페이스 (읽기/쓰기 접근)
기존 zip 파일을 만들거나 수정하려면 데이터베이스 스키마에 "zipfile" 가상 테이블을 만들어야 해요. CREATE VIRTUAL TABLE 문은 유일한 인자로 zip 파일의 경로를 기대해요. 예를 들어 현재 디렉터리의 zip 파일 "test.zip"에 쓰려면 다음과 같이 zipfile 테이블을 만들 수 있어요:
CREATE VIRTUAL TABLE temp.zip USING zipfile('test.zip');
이런 가상 테이블은 이전 절에서 설명한 테이블 값 함수와 같은 열을 가져요. 테이블 값 함수와 같은 방식으로 SELECT 문을 사용해 읽을 수 있어요.
가상 테이블 인터페이스를 사용해 가상 테이블에 새 행을 삽입함으로써 zip 아카이브에 새 항목을 추가할 수 있고, 행을 삭제해 항목을 제거하거나 행을 갱신해 수정할 수 있어요.
3.2.1. 중요한 제한 사항 - 비트랜잭션 (Non-transactional)
zipfile 확장은 트랜잭션이 아니에요. 갱신은 원자적이지 않아요. ROLLBACK 명령은 동작하지 않아요. (그냥 COMMIT을 수행할 뿐이에요.) 여러 프로세스가 같은 zip 아카이브에 동시에 쓰려고 하면 서로 간섭하고 zip 아카이브는 결국 손상될 거예요. 프로세스가 zip 아카이브 수정 중간에 종료되거나 죽으면 zip 아카이브가 손상될 수 있어요.
3.2.2. zip 아카이브에 항목 추가하기 (Adding Entries to a Zip Archive)
새 행을 삽입함으로써 zip 아카이브에 항목을 추가할 수 있어요. 가장 쉬운 방법은 "name"과 "data" 열에만 값을 지정하고 나머지 필드는 zipfile이 적절한 기본값을 채우게 하는 것이에요. 디렉터리를 아카이브에 삽입하려면 "data" 열을 NULL로 설정해요. 예를 들어 "dir1" 디렉터리와 "abcdefghi" 텍스트를 담은 "m.txt" 파일을 zip 아카이브 "test.zip"에 추가하려면:
INSERT INTO temp.zip(name, data) VALUES('dir1', NULL); -- 디렉터리 추가
INSERT INTO temp.zip(name, data) VALUES('m.txt', 'abcdefghi'); -- 일반 파일 추가
디렉터리를 삽입할 때 "name" 값이 '/' 문자로 끝나지 않으면 zipfile 모듈이 '/'를 덧붙여요. 이는 zip 아카이브를 다루는 다른 프로그램(특히 "info-zip")과의 호환성을 위해 필요해요.
심볼릭 링크를 삽입하려면 사용자가 "mode" 값도 제공해야 해요. 예를 들어 "link.txt"에서 "m.txt"로의 심볼릭 링크를 추가하려면:
INSERT INTO temp.zip(name, mode, data) VALUES('link.txt', 'lrwxrw-rw-', 'm.txt');
각 INSERT 문의 일부로 지정되는 값에 적용되는 규칙과 주의 사항은 다음과 같아요:
| 열 | 참고 |
|---|---|
| name | name 열에는 NULL이 아닌 텍스트 값을 지정해야 해요. 지정된 이름이 아카이브에 이미 존재하면 오류예요. |
| mode | mode 열에 NULL이 삽입되면, "sz", "data", "rawdata" 열에 지정된 값이 새 항목이 디렉터리인지 여부를 나타내는지에 따라 새 아카이브 항목의 모드가 자동으로 33188(-rw-r--r--) 또는 16877(drwxr-xr-x)로 설정돼요. |
| 지정된 값이 정수(또는 정수처럼 보이는 텍스트)이면 그대로 삽입돼요. 값이 유효한 UNIX 모드가 아니면 일부 프로그램은 아카이브에서 파일을 추출할 때 예상치 못한 동작을 할 수 있어요. | |
| 마지막으로, 이 열에 지정된 값이 정수도 NULL도 아니면 "ls -l" 명령이 출력하는 것과 유사한 UNIX 권한 문자열(예: "-rw-r--r--", "drwxr-xr-x" 등)로 간주돼요. 이 경우 문자열을 파싱할 수 없으면 오류예요. | |
| mtime | mtime 열에 NULL이 삽입되면 새 항목의 타임스탬프가 현재 시간으로 설정돼요. 그렇지 않으면 지정된 값이 정수로 해석되어 그대로 사용돼요. |
| sz | 이 열은 NULL로 설정해야 해요. 이 열에 NULL이 아닌 값이 삽입되거나 UPDATE 문으로 새 NULL이 아닌 값이 제공되면 오류예요. |
| rawdata | 이 열은 NULL로 설정해야 해요. 이 열에 NULL이 아닌 값이 삽입되거나 UPDATE 문으로 새 NULL이 아닌 값이 제공되면 오류예요. |
| data | 디렉터리를 아카이브에 삽입하려면 이 필드를 NULL로 설정해야 해요. 이 경우 "mode" 열에 값이 명시적으로 지정되었다면 그것은 디렉터리와 일관되어야 해요. (즉 (mode & 0040000)=0040000이 참이어야 해요.) |
| 그렇지 않으면 이 필드에 삽입되는 값은 일반 파일의 파일 내용 또는 심볼릭 링크의 대상이에요. | |
| method | 이 필드는 정수 값 0과 8 중 하나 또는 NULL로 설정해야 해요. |
| 디렉터리 항목의 경우 이 필드에 삽입된 어떤 값도 무시돼요. 그렇지 않고 0으로 설정되면 파일 데이터 또는 심볼릭 링크 대상이 zip 아카이브에 그대로 저장되고 압축 방법이 0으로 설정돼요. 8로 설정되면 저장 전에 파일 데이터나 링크 대상이 deflate 압축으로 압축되고 압축 방법이 8로 설정돼요. 마지막으로 NULL 값이 이 필드에 쓰이면 zipfile 모듈이 저장 전에 데이터를 압축할지 여부를 자동으로 결정해요. |
INSERT 문의 일부로 rowid 필드에 명시적 값을 지정하는 것은 지원되지 않아요. 지정된 어떤 값도 무시돼요.
3.2.3. zip 아카이브 항목 삭제하기 (Deleting Zip Archive Entries)
해당 행을 삭제함으로써 기존 zip 아카이브에서 레코드를 제거할 수 있어요. 예를 들어 위에서 만든 가상 테이블을 사용해 zip 아카이브 "test.zip"에서 파일 "m.txt"를 제거하려면:
DELETE FROM temp.zip WHERE name = 'm.txt';
zip 아카이브에서 레코드를 삭제해도 아카이브 내에서 사용된 공간이 회수되지는 않는다는 점에 주의하세요. 단지 아카이브의 "중앙 디렉터리 구조(Central Directory Structure)"에서 항목을 제거해 그 항목에 접근할 수 없게 할 뿐이에요. 이 비효율을 해결하는 한 가지 방법은 편집된 아카이브의 내용을 기반으로 새 zip 아카이브를 만드는 것이에요. 예를 들어 가상 테이블 temp.zzz를 통해 접근하는 아카이브를 편집한 후:
-- 새 빈 아카이브 만들기:
CREATE VIRTUAL TABLE temp.newzip USING zipfile('new.zip');
-- 기존 아카이브의 내용을 새 아카이브로 복사하기
INSERT INTO temp.newzip(name, mode, mtime, data, method)
SELECT name, mode, mtime, data, method FROM temp.zzz;
3.2.4. 기존 zip 아카이브 항목 갱신하기 (Updating Existing Zip Archive Entries)
기존 zip 아카이브 항목은 UPDATE 문을 사용해 수정할 수 있어요.
zipfile 가상 테이블의 가장 왼쪽 세 열 "name", "mode", "mtime"은 각각 같은 열에 삽입될 수 있는 어떤 값으로도 설정할 수 있어요(위 참고). "mode"나 "mtime"이 NULL로 설정되면, 최종 값은 NULL 값의 INSERT에 대해 설명된 대로 결정돼요. - "mtime"은 현재 시간이고, "mode"는 zipfile 테이블의 다음 네 열에 지정된 값이 그 항목이 디렉터리인지 파일인지를 나타내는지에 따라 33188 또는 16877이에요.
sz나 rawdata 필드를 NULL 외의 어떤 값으로 설정하려는 것은 오류예요.
data와 method 열도 위 INSERT에 대해 설명된 대로 설정할 수 있어요.
3.3. zipfile() 집계 함수 (The zipfile() Aggregate Function)
zipfile() 집계 함수를 사용해 새 zip 아카이브를 전적으로 메모리 안에서 구성할 수 있어요. 집계 함수가 방문하는 각 행은 zip 아카이브에 항목 하나를 추가해요. 반환되는 값은 전체 아카이브 이미지를 담은 blob이에요.
zipfile() 집계 함수는 2, 4 또는 5개의 인자로 호출할 수 있어요. 5개 인자로 호출하면 아카이브에 추가되는 항목은 같은 값들을 zipfile 가상 테이블의 "name", "mode", "mtime", "data", "method" 열에 삽입하는 것과 동등해요.
zipfile()이 2개 인자로 호출되면 아카이브에 추가되는 항목은 같은 두 값을 zipfile 가상 테이블의 "name"과 "data" 열에 삽입하고 나머지 모든 값을 NULL로 설정해 추가되는 것과 동등해요. 4개 인자로 호출되면 그 4개 값을 "name", "mode", "mtime", "data" 열에 삽입하는 것과 동등해요. 즉 다음 쿼리 쌍은 동등해요:
SELECT zipfile(name, data) ...
SELECT zipfile(name, NULL, NULL, data, NULL) ...
SELECT zipfile(name, mode, mtime, data) ...
SELECT zipfile(name, mode, mtime, data, NULL) ...
예를 들어 각각 "abc"와 "123" 텍스트를 담은 "a.txt"와 "b.txt" 두 텍스트 파일을 담은 아카이브를 만들려면:
WITH contents(name, data) AS (
VALUES('a.txt', 'abc') UNION ALL
VALUES('b.txt', '123')
)
SELECT zipfile(name, data) FROM contents;