Skip to content

Commit fffbd0f

Browse files
committed
Add initial version of sqlite-brotli-compress
0 parents  commit fffbd0f

File tree

5 files changed

+1209
-0
lines changed

5 files changed

+1209
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
brotlicompress.so

Makefile

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
all: brotlicompress.so check
2+
.PHONY: all
3+
4+
build: brotlicompress.so
5+
@:
6+
.PHONY: build
7+
8+
brotlicompress.so:
9+
gcc -Wall -O3 -fPIC brotlicompress.c -o brotlicompress.so -lbrotlienc -lbrotlidec -lbrotlicommon -shared
10+
11+
check:
12+
valgrind --leak-check=full \
13+
sqlite3 :memory: \
14+
'.load ./brotlicompress' \
15+
'.mode csv' \
16+
'.import testdata/hn.csv hn' \
17+
'.mode line' \
18+
'SELECT record as error_record_tag FROM hn WHERE CAST(brotli_decompress(brotli_compress(record, 5)) AS TEXT) != record;' | \
19+
grep -vz error_record_tag
20+
.PHONY: check
21+
22+
clean:
23+
rm brotlicompress.so
24+
.PHONY: clean

README.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Why?
2+
3+
`sqlite-brotli-compress` extends sqlite3 with 2 functions:
4+
5+
```
6+
brotli_compress(value, compression_level)
7+
brotli_decompress(value)
8+
```
9+
10+
It is convinient to use compression to store some raw data.
11+
12+
# How to use
13+
14+
```buildoutcfg
15+
make build
16+
17+
sqlite3 :memory: \
18+
'.load ./brotlicompress' \
19+
'CREATE TABLE compressed_data (id INTEGER PRIMARY KEY, payload BLOB NOT NULL);' \
20+
'CREATE VIEW data AS SELECT id, brotli_decompress(payload) AS payload FROM compressed_data;' \
21+
'INSERT INTO compressed_data (payload) VALUES (brotli_compress("some_string_that_is_easy_to_compressssssssssss", 8));' \
22+
'SELECT *, length(payload) FROM data;' \
23+
'SELECT id, length(payload) FROM compressed_data;'
24+
25+
```
26+

brotlicompress.c

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#include "sqlite3ext.h"
2+
SQLITE_EXTENSION_INIT1
3+
#include <stdio.h>
4+
#include <brotli/encode.h>
5+
#include <brotli/decode.h>
6+
7+
static void brotliCompressSqliteFunction(
8+
sqlite3_context *context,
9+
int argc,
10+
sqlite3_value **argv
11+
)
12+
{
13+
size_t blob_size = sqlite3_value_bytes(argv[0]);
14+
const uint8_t *blob_ptr = sqlite3_value_blob(argv[0]);
15+
int brotli_quality = sqlite3_value_int(argv[1]);
16+
if (!(brotli_quality >= BROTLI_MIN_QUALITY && brotli_quality <= BROTLI_MAX_QUALITY)) {
17+
sqlite3_result_error(context, "brotli: wrong quality value", -1);
18+
return;
19+
}
20+
size_t output_blob_size = BrotliEncoderMaxCompressedSize(blob_size);
21+
uint8_t *output_blob_ptr = sqlite3_malloc(output_blob_size);
22+
if (output_blob_ptr == NULL) {
23+
sqlite3_result_error(context, "brotli: error allocating memory", -1);
24+
}
25+
26+
BROTLI_BOOL rc = BrotliEncoderCompress(
27+
brotli_quality,
28+
BROTLI_DEFAULT_WINDOW,
29+
BROTLI_MODE_TEXT,
30+
blob_size,
31+
blob_ptr,
32+
&output_blob_size,
33+
output_blob_ptr
34+
);
35+
36+
if (rc == BROTLI_TRUE) {
37+
sqlite3_result_blob(context, output_blob_ptr, output_blob_size, sqlite3_free);
38+
} else {
39+
sqlite3_free(output_blob_ptr);
40+
sqlite3_result_error(context, "brotli: error compressing data", -1);
41+
}
42+
}
43+
44+
static int brotliDecoderDecompressWithRealloc(
45+
size_t encoded_size,
46+
const uint8_t *encoded_buffer,
47+
size_t *decoded_size,
48+
uint8_t **decoded_buffer
49+
) {
50+
BrotliDecoderState* bds;
51+
size_t total_out = 0;
52+
size_t buffer_size = 0;
53+
uint8_t *buffer = NULL;
54+
uint8_t *next_out = NULL;
55+
size_t avail_out = 0;
56+
bds = BrotliDecoderCreateInstance(NULL, NULL, NULL);
57+
if (bds == NULL) {
58+
return BROTLI_DECODER_RESULT_ERROR;
59+
}
60+
61+
BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
62+
while (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
63+
size_t new_buffer_size = buffer_size * 2 + 1024;
64+
uint8_t *new_buffer = sqlite3_realloc(buffer, new_buffer_size);
65+
if (new_buffer == NULL) {
66+
BrotliDecoderDestroyInstance(bds);
67+
sqlite3_free(buffer);
68+
return BROTLI_DECODER_RESULT_ERROR;
69+
}
70+
ssize_t shift = next_out - buffer;
71+
buffer = new_buffer;
72+
next_out = new_buffer + shift;
73+
avail_out += new_buffer_size - buffer_size;
74+
buffer_size = new_buffer_size;
75+
result = BrotliDecoderDecompressStream(
76+
bds,
77+
&encoded_size,
78+
&encoded_buffer,
79+
&avail_out,
80+
&next_out,
81+
&total_out);
82+
}
83+
84+
BrotliDecoderDestroyInstance(bds);
85+
if (result != BROTLI_DECODER_RESULT_SUCCESS) {
86+
sqlite3_free(buffer);
87+
return BROTLI_DECODER_RESULT_ERROR;
88+
}
89+
*decoded_size = total_out;
90+
*decoded_buffer = buffer;
91+
return BROTLI_DECODER_RESULT_SUCCESS;
92+
}
93+
94+
static void brotliDecompressSqliteFunction(
95+
sqlite3_context *context,
96+
int argc,
97+
sqlite3_value **argv
98+
){
99+
const size_t blob_size = sqlite3_value_bytes(argv[0]);
100+
const uint8_t *blob_ptr = sqlite3_value_blob(argv[0]);
101+
size_t output_blob_size;
102+
uint8_t *output_blob_ptr;
103+
BrotliDecoderResult rc = brotliDecoderDecompressWithRealloc(
104+
blob_size,
105+
blob_ptr,
106+
&output_blob_size,
107+
&output_blob_ptr
108+
);
109+
110+
if ( rc==BROTLI_DECODER_RESULT_SUCCESS ){
111+
sqlite3_result_blob(context, output_blob_ptr, output_blob_size, sqlite3_free);
112+
} else {
113+
sqlite3_result_error(context, "brotli: error decompressing data", -1);
114+
}
115+
}
116+
117+
118+
#ifdef _WIN32
119+
__declspec(dllexport)
120+
#endif
121+
int sqlite3_brotlicompress_init(
122+
sqlite3 *db,
123+
char **_,
124+
const sqlite3_api_routines *pApi
125+
){
126+
SQLITE_EXTENSION_INIT2(pApi);
127+
int rc = sqlite3_create_function(
128+
db,
129+
"brotli_compress",
130+
2,
131+
SQLITE_UTF8,
132+
0,
133+
brotliCompressSqliteFunction,
134+
0,
135+
0
136+
);
137+
if ( rc!=SQLITE_OK ) {
138+
return rc;
139+
}
140+
141+
rc = sqlite3_create_function(
142+
db,
143+
"brotli_decompress",
144+
1,
145+
SQLITE_UTF8 | SQLITE_DETERMINISTIC,
146+
0,
147+
brotliDecompressSqliteFunction,
148+
0,
149+
0
150+
);
151+
if ( rc!=SQLITE_OK ) {
152+
return rc;
153+
}
154+
155+
return SQLITE_OK;
156+
}
157+

0 commit comments

Comments
 (0)