Skip to content

Commit f54f9ed

Browse files
committed
Initial import of Fast Cache Engine
Add the Fast Cache Engine project: C library sources and public headers, CLI, Python ctypes binding, CMake build, documentation (English and Chinese), and project metadata. Includes multiple backend implementations, codec support, schema and API headers, and a .github Actions CI workflow that builds the project and runs generated smoke tests for the C API, CLI, and Python binding. Also add .gitignore and README files with quick-start and build instructions.
0 parents  commit f54f9ed

41 files changed

Lines changed: 7073 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
build-test:
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
os: [ubuntu-latest, macos-latest, windows-latest]
13+
runs-on: ${{ matrix.os }}
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: actions/setup-python@v5
17+
with:
18+
python-version: "3.x"
19+
20+
- name: Configure
21+
run: cmake -S . -B build -DFCE_ENABLE_ZSTD=ON
22+
23+
- name: Build
24+
run: cmake --build build --config Release --parallel 2
25+
26+
- name: Generate C API smoke test
27+
shell: bash
28+
run: |
29+
mkdir -p ci_smoke
30+
cat > ci_smoke/CMakeLists.txt <<'EOF'
31+
cmake_minimum_required(VERSION 3.16)
32+
project(fce_ci_smoke LANGUAGES C)
33+
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/.." fce)
34+
add_executable(ci_smoke ci_smoke.c)
35+
target_link_libraries(ci_smoke PRIVATE fast_cache_engine)
36+
EOF
37+
cat > ci_smoke/ci_smoke.c <<'EOF'
38+
#include "fce_cache.h"
39+
#include <stdint.h>
40+
#include <stdio.h>
41+
#include <string.h>
42+
43+
#define CHECK_OK(expr) do { FceStatus st__ = (expr); if (st__ != FCE_OK) { \
44+
fprintf(stderr, "%s failed: %s\n", #expr, fce_status_string(st__)); return 1; } } while (0)
45+
#define CHECK(expr) do { if (!(expr)) { fprintf(stderr, "check failed: %s\n", #expr); return 1; } } while (0)
46+
47+
static int check_bytes(const void *p, size_t n, const char *s) {
48+
return n == strlen(s) && memcmp(p, s, n) == 0;
49+
}
50+
51+
int main(void) {
52+
const void *v = NULL;
53+
size_t n = 0;
54+
FceBuilder *b = NULL;
55+
FceReader *r = NULL;
56+
FceIterator *it = NULL;
57+
58+
FceSchema s = fce_schema_default();
59+
s.allow_duplicate_put = 1;
60+
CHECK_OK(fce_builder_open("ci_sorted_cache", &s, &b));
61+
CHECK_OK(fce_builder_put(b, "k-a13f", 6, "v-01", 4));
62+
CHECK_OK(fce_builder_put(b, "k-a13f", 6, "v-02", 4));
63+
CHECK_OK(fce_builder_put_u128(b, 7, 9, "v-9c", 4));
64+
CHECK_OK(fce_builder_freeze(b));
65+
CHECK_OK(fce_builder_close(b));
66+
CHECK_OK(fce_reader_open_expected("ci_sorted_cache", &s, &r));
67+
CHECK_OK(fce_reader_get(r, "k-a13f", 6, &v, &n));
68+
CHECK(check_bytes(v, n, "v-02"));
69+
CHECK_OK(fce_reader_get_u128(r, 7, 9, &v, &n));
70+
CHECK(check_bytes(v, n, "v-9c"));
71+
fce_reader_close(r);
72+
73+
s = fce_schema_default();
74+
s.backend = FCE_BACKEND_DIRECT_TABLE;
75+
s.key_kind = FCE_KEY_U64;
76+
s.fixed_key_size = 8;
77+
s.user_flags = FCE_FLAG_ALLOW_SPARSE_DIRECT_TABLE;
78+
CHECK_OK(fce_builder_open("ci_direct_cache", &s, &b));
79+
CHECK_OK(fce_builder_put_u64(b, 42, "v-d4", 4));
80+
CHECK_OK(fce_builder_freeze(b));
81+
CHECK_OK(fce_builder_close(b));
82+
CHECK_OK(fce_reader_open_expected("ci_direct_cache", &s, &r));
83+
CHECK_OK(fce_reader_get_u64(r, 42, &v, &n));
84+
CHECK(check_bytes(v, n, "v-d4"));
85+
fce_reader_close(r);
86+
87+
s = fce_schema_default();
88+
s.backend = FCE_BACKEND_RADIX;
89+
s.lookup = FCE_LOOKUP_PREFIX;
90+
CHECK_OK(fce_builder_open("ci_radix_cache", &s, &b));
91+
CHECK_OK(fce_builder_put(b, "p-01a", 5, "x", 1));
92+
CHECK_OK(fce_builder_put(b, "p-01b", 5, "y", 1));
93+
CHECK_OK(fce_builder_put(b, "q-99z", 5, "z", 1));
94+
CHECK_OK(fce_builder_freeze(b));
95+
CHECK_OK(fce_builder_close(b));
96+
CHECK_OK(fce_reader_open_expected("ci_radix_cache", &s, &r));
97+
CHECK_OK(fce_reader_prefix_scan(r, "p-", 2, &it));
98+
size_t hits = 0;
99+
const void *key_ptr = NULL;
100+
const void *value_ptr = NULL;
101+
size_t key_len = 0;
102+
size_t value_len = 0;
103+
while (fce_iterator_next(it, &key_ptr, &key_len, &value_ptr, &value_len) == FCE_OK) hits++;
104+
CHECK(hits == 2);
105+
fce_iterator_close(it);
106+
fce_reader_close(r);
107+
108+
s = fce_schema_default();
109+
s.backend = FCE_BACKEND_MPH;
110+
CHECK_OK(fce_builder_open("ci_mph_cache", &s, &b));
111+
CHECK_OK(fce_builder_put(b, "m-a7", 4, "x7", 2));
112+
CHECK_OK(fce_builder_put(b, "m-b8", 4, "y8", 2));
113+
CHECK_OK(fce_builder_freeze(b));
114+
CHECK_OK(fce_builder_close(b));
115+
CHECK_OK(fce_reader_open_expected("ci_mph_cache", &s, &r));
116+
CHECK_OK(fce_reader_get(r, "m-b8", 4, &v, &n));
117+
CHECK(check_bytes(v, n, "y8"));
118+
CHECK(fce_reader_get(r, "missing", 7, &v, &n) == FCE_ERR_NOT_FOUND);
119+
fce_reader_close(r);
120+
121+
s = fce_schema_default();
122+
s.backend = FCE_BACKEND_LOG;
123+
s.allow_duplicate_put = 1;
124+
CHECK_OK(fce_builder_open("ci_log_cache", &s, &b));
125+
CHECK_OK(fce_builder_put(b, "l-9x", 4, "v-a", 3));
126+
CHECK_OK(fce_builder_freeze(b));
127+
CHECK_OK(fce_builder_close(b));
128+
CHECK_OK(fce_log_append("ci_log_cache", "l-9x", 4, "v-b", 3));
129+
CHECK_OK(fce_reader_open_expected("ci_log_cache", &s, &r));
130+
CHECK_OK(fce_reader_get(r, "l-9x", 4, &v, &n));
131+
CHECK(check_bytes(v, n, "v-b"));
132+
fce_reader_close(r);
133+
CHECK_OK(fce_compact("ci_log_cache", FCE_BACKEND_SORTED_INDEX, "ci_compact_cache"));
134+
135+
int32_t values[] = {5, 5, 5, 9};
136+
void *enc = NULL;
137+
void *dec = NULL;
138+
size_t enc_len = 0;
139+
size_t dec_len = 0;
140+
CHECK_OK(fce_codec_encode(FCE_CODEC_RLE, values, sizeof(values), &enc, &enc_len));
141+
CHECK_OK(fce_codec_decode(FCE_CODEC_RLE, enc, enc_len, &dec, &dec_len));
142+
CHECK(dec_len == sizeof(values) && memcmp(dec, values, sizeof(values)) == 0);
143+
fce_free(enc);
144+
fce_free(dec);
145+
146+
FceMemoryStats stats;
147+
CHECK_OK(fce_memory_stats(&stats));
148+
CHECK(stats.active_allocations == 0);
149+
CHECK(stats.active_bytes == 0);
150+
return 0;
151+
}
152+
EOF
153+
154+
- name: Build and run C API smoke test
155+
shell: bash
156+
run: |
157+
cmake -S ci_smoke -B ci_smoke_build -DFCE_ENABLE_ZSTD=ON
158+
cmake --build ci_smoke_build --config Release --parallel 2
159+
python - <<'PY'
160+
from pathlib import Path
161+
import subprocess
162+
candidates = [
163+
Path("ci_smoke_build/ci_smoke"),
164+
Path("ci_smoke_build/Release/ci_smoke.exe"),
165+
Path("ci_smoke_build/Debug/ci_smoke.exe"),
166+
]
167+
exe = next((p for p in candidates if p.exists()), None)
168+
if exe is None:
169+
raise SystemExit("ci_smoke executable not found")
170+
subprocess.run([str(exe)], check=True)
171+
PY
172+
173+
- name: CLI smoke test
174+
shell: bash
175+
run: |
176+
python - <<'PY'
177+
from pathlib import Path
178+
import json
179+
import shutil
180+
import struct
181+
import subprocess
182+
import tempfile
183+
184+
candidates = [
185+
Path("build/fce"),
186+
Path("build/fce.exe"),
187+
Path("build/Release/fce.exe"),
188+
Path("build/Debug/fce.exe"),
189+
]
190+
exe = next((p for p in candidates if p.exists()), None)
191+
if exe is None:
192+
raise SystemExit("fce executable not found")
193+
194+
root = Path(tempfile.mkdtemp(prefix="fce-cli-"))
195+
try:
196+
schema = root / "schema.json"
197+
records = root / "records.fce"
198+
cache = root / "cache"
199+
schema.write_text(json.dumps({
200+
"backend": "radix",
201+
"lookup": "prefix",
202+
"key_kind": "bytes",
203+
"value_kind": "var_record",
204+
"allow_duplicate_put": True,
205+
}), encoding="utf-8")
206+
records.write_bytes(struct.pack("<QQ", 5, 3) + b"r-a7x" + b"v9q")
207+
subprocess.run([str(exe), "build", "--schema", str(schema), "--input", str(records), "--output", str(cache)], check=True)
208+
subprocess.run([str(exe), "validate", str(cache)], check=True)
209+
inspect = subprocess.run([str(exe), "inspect", str(cache)], check=True, text=True, stdout=subprocess.PIPE).stdout
210+
if "backend=radix" not in inspect:
211+
raise SystemExit(inspect)
212+
dump = subprocess.run([str(exe), "dump", str(cache), "--limit", "1"], check=True, text=True, stdout=subprocess.PIPE).stdout
213+
if "key_len=5" not in dump:
214+
raise SystemExit(dump)
215+
finally:
216+
shutil.rmtree(root, ignore_errors=True)
217+
PY
218+
219+
- name: Python binding smoke test
220+
shell: bash
221+
run: |
222+
python - <<'PY'
223+
from pathlib import Path
224+
import shutil
225+
import sys
226+
import tempfile
227+
228+
sys.path.insert(0, str(Path("python").resolve()))
229+
from fast_cache_engine import (
230+
CacheBuilder,
231+
CacheReader,
232+
CacheSchema,
233+
export_sqlite,
234+
import_sqlite,
235+
version_string,
236+
)
237+
238+
root = Path(tempfile.mkdtemp(prefix="fce-python-"))
239+
try:
240+
schema = CacheSchema(backend="sorted_index")
241+
cache = root / "cache"
242+
with CacheBuilder(cache, schema) as builder:
243+
builder.put(b"k-9f", b"v-2c")
244+
builder.freeze()
245+
with CacheReader(cache) as reader:
246+
assert bytes(reader.get(b"k-9f")) == b"v-2c"
247+
248+
db = root / "cache.sqlite"
249+
restored = root / "restored"
250+
export_sqlite(cache, db)
251+
import_sqlite(db, restored, schema)
252+
with CacheReader(restored) as reader:
253+
assert bytes(reader.get(b"k-9f")) == b"v-2c"
254+
255+
assert version_string()
256+
finally:
257+
shutil.rmtree(root, ignore_errors=True)
258+
PY

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
build/
2+
build_*/
3+
cmake-build-*/
4+
ci_smoke/
5+
ci_smoke_build/
6+
ci_*_cache/
7+
.idea/
8+
__pycache__/
9+
*.pyc
10+
tmp_*_cache/
11+
tmp_*.fce
12+
tmp_*.json

CMakeLists.txt

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
project(fast_cache_engine VERSION 1.0.0 LANGUAGES C)
3+
4+
set(CMAKE_C_STANDARD 11)
5+
set(CMAKE_C_STANDARD_REQUIRED ON)
6+
set(CMAKE_C_EXTENSIONS OFF)
7+
8+
option(FCE_ENABLE_ZSTD "Enable zstd codec when libzstd is available" ON)
9+
10+
if(FCE_ENABLE_ZSTD)
11+
find_path(ZSTD_INCLUDE_DIR zstd.h)
12+
find_library(ZSTD_LIBRARY NAMES zstd libzstd)
13+
if(ZSTD_INCLUDE_DIR AND ZSTD_LIBRARY)
14+
set(FCE_HAVE_ZSTD ON)
15+
endif()
16+
endif()
17+
18+
set(FCE_PUBLIC_HEADERS
19+
include/fce_builder.h
20+
include/fce_cache.h
21+
include/fce_error.h
22+
include/fce_reader.h
23+
include/fce_schema.h
24+
)
25+
26+
set(FCE_LIBRARY_SOURCES
27+
src/api/fce_builder_api.c
28+
src/api/fce_reader_api.c
29+
src/backends/fce_backend_direct.c
30+
src/backends/fce_backend_log.c
31+
src/backends/fce_backend_mph.c
32+
src/backends/fce_backend_radix.c
33+
src/backends/fce_backend_sorted.c
34+
src/codecs/fce_codecs.c
35+
src/core/fce_build_records.c
36+
src/core/fce_crc.c
37+
src/core/fce_file.c
38+
src/core/fce_hash.c
39+
src/core/fce_manifest.c
40+
src/core/fce_memory.c
41+
src/core/fce_planner.c
42+
src/core/fce_schema.c
43+
src/core/fce_version.c
44+
)
45+
46+
add_library(fast_cache_engine
47+
${FCE_LIBRARY_SOURCES}
48+
${FCE_PUBLIC_HEADERS}
49+
)
50+
51+
target_include_directories(fast_cache_engine
52+
PUBLIC
53+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
54+
$<INSTALL_INTERFACE:include>
55+
PRIVATE
56+
${CMAKE_CURRENT_SOURCE_DIR}/src/internal
57+
)
58+
59+
if(WIN32)
60+
target_compile_definitions(fast_cache_engine PRIVATE _CRT_SECURE_NO_WARNINGS)
61+
endif()
62+
if(FCE_HAVE_ZSTD)
63+
target_compile_definitions(fast_cache_engine PRIVATE FCE_HAVE_ZSTD)
64+
target_include_directories(fast_cache_engine PRIVATE ${ZSTD_INCLUDE_DIR})
65+
target_link_libraries(fast_cache_engine PRIVATE ${ZSTD_LIBRARY})
66+
endif()
67+
68+
add_library(fast_cache_engine_shared SHARED
69+
${FCE_LIBRARY_SOURCES}
70+
${FCE_PUBLIC_HEADERS}
71+
)
72+
target_include_directories(fast_cache_engine_shared
73+
PUBLIC
74+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
75+
$<INSTALL_INTERFACE:include>
76+
PRIVATE
77+
${CMAKE_CURRENT_SOURCE_DIR}/src/internal
78+
)
79+
target_compile_definitions(fast_cache_engine_shared PRIVATE FCE_BUILD_SHARED FCE_BUILDING_LIBRARY)
80+
set_target_properties(fast_cache_engine_shared PROPERTIES
81+
OUTPUT_NAME fast_cache_engine
82+
ARCHIVE_OUTPUT_NAME fast_cache_engine_shared)
83+
if(WIN32)
84+
target_compile_definitions(fast_cache_engine_shared PRIVATE _CRT_SECURE_NO_WARNINGS)
85+
endif()
86+
if(FCE_HAVE_ZSTD)
87+
target_compile_definitions(fast_cache_engine_shared PRIVATE FCE_HAVE_ZSTD)
88+
target_include_directories(fast_cache_engine_shared PRIVATE ${ZSTD_INCLUDE_DIR})
89+
target_link_libraries(fast_cache_engine_shared PRIVATE ${ZSTD_LIBRARY})
90+
endif()
91+
92+
add_executable(fce src/cli/fce_cli.c)
93+
target_link_libraries(fce PRIVATE fast_cache_engine)
94+
95+
install(TARGETS fast_cache_engine fast_cache_engine_shared fce
96+
RUNTIME DESTINATION bin
97+
LIBRARY DESTINATION lib
98+
ARCHIVE DESTINATION lib)
99+
install(DIRECTORY include/ DESTINATION include)

0 commit comments

Comments
 (0)