Skip to content

Commit 1c25132

Browse files
committed
Added support for path and snippet in the memory_search virtual table
1 parent 3dc525a commit 1c25132

5 files changed

Lines changed: 62 additions & 43 deletions

File tree

API.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -342,16 +342,16 @@ SELECT * FROM memory_search WHERE query = 'search text';
342342
|--------|------|-------------|
343343
| `query` | TEXT (HIDDEN) | Search query (required in WHERE clause) |
344344
| `hash` | INTEGER | Content hash identifier |
345-
| `path` | TEXT | Source path or generated UUID |
345+
| `path` | TEXT | Source file path or generated UUID for text content |
346346
| `context` | TEXT | Context label (NULL if not set) |
347-
| `snippet` | TEXT | Text snippet from matching chunk |
348-
| `score` | REAL | Combined similarity score (0.0 - 1.0) |
347+
| `snippet` | TEXT | Text snippet from the matching chunk |
348+
| `ranking` | REAL | Combined similarity score (0.0 - 1.0) |
349349

350350
**Notes:**
351351
- Requires sqlite-vector extension loaded first
352352
- Performs hybrid search combining vector similarity and FTS5
353353
- Results are ranked by combined score
354-
- Limited by `max_items` setting (default: 20)
354+
- Limited by `max_results` setting (default: 20)
355355
- Filtered by `min_score` setting (default: 0.7)
356356
- Updates `last_accessed` timestamp if `update_access` is enabled
357357

@@ -360,11 +360,11 @@ SELECT * FROM memory_search WHERE query = 'search text';
360360
-- Basic search
361361
SELECT * FROM memory_search WHERE query = 'database indexing strategies';
362362

363-
-- Search with score filter
364-
SELECT path, snippet, score
363+
-- Search with ranking filter
364+
SELECT path, snippet, ranking
365365
FROM memory_search
366366
WHERE query = 'how to optimize queries'
367-
AND score > 0.8;
367+
AND ranking > 0.8;
368368

369369
-- Search within a specific context
370370
SELECT * FROM memory_search
@@ -389,7 +389,7 @@ AND context = 'meetings';
389389
| `skip_html` | INTEGER | 1 | Strip HTML tags when parsing |
390390
| `extensions` | TEXT | "md,mdx" | Comma-separated file extensions to process |
391391
| `engine_warmup` | INTEGER | 0 | Warm up engine on model load (compiles GPU shaders) |
392-
| `max_items` | INTEGER | 20 | Maximum search results |
392+
| `max_results` | INTEGER | 20 | Maximum search results |
393393
| `fts_enabled` | INTEGER | 1 | Enable FTS5 in hybrid search |
394394
| `vector_weight` | REAL | 0.5 | Weight for vector similarity in scoring |
395395
| `text_weight` | REAL | 0.5 | Weight for FTS in scoring |
@@ -449,7 +449,7 @@ SELECT memory_add_text('SQLite is a C library that provides a lightweight disk-b
449449
SELECT memory_add_directory('/docs/sqlite', 'sqlite-docs');
450450

451451
-- Search
452-
SELECT path, snippet, score
452+
SELECT path, snippet, ranking
453453
FROM memory_search
454454
WHERE query = 'how does SQLite store data on disk';
455455

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,17 @@ systems, and AI applications.', 'concepts');
9292
SELECT memory_add_directory('/path/to/docs', 'project-docs');
9393

9494
-- Search your memory semantically
95-
SELECT path, snippet, score
95+
SELECT path, snippet, ranking
9696
FROM memory_search
9797
WHERE query = 'how do databases store information efficiently';
9898

9999
-- Results ranked by semantic similarity + keyword matching
100-
-- ┌──────────────┬─────────────────────────────────────┬───────┐
101-
-- │ path │ snippet │ score
102-
-- ├──────────────┼─────────────────────────────────────┼───────┤
103-
-- │ (uuid) │ SQLite is a C-language library... │ 0.89 │
104-
-- │ (uuid) │ Vector databases store data as... │ 0.82 │
105-
-- └──────────────┴─────────────────────────────────────┴───────┘
100+
-- ┌──────────────┬─────────────────────────────────────┬─────────
101+
-- │ path │ snippet │ ranking
102+
-- ├──────────────┼─────────────────────────────────────┼─────────
103+
-- │ (uuid) │ SQLite is a C-language library... │ 0.89
104+
-- │ (uuid) │ Vector databases store data as... │ 0.82
105+
-- └──────────────┴─────────────────────────────────────┴─────────
106106
```
107107

108108
### Example: Building an AI Agent with Memory
@@ -127,9 +127,9 @@ def remember(content, context="conversation"):
127127
# Retrieve relevant memories
128128
def recall(query, min_score=0.7):
129129
cursor = conn.execute("""
130-
SELECT snippet, score FROM memory_search
131-
WHERE query = ? AND score > ?
132-
ORDER BY score DESC
130+
SELECT snippet, ranking FROM memory_search
131+
WHERE query = ? AND ranking > ?
132+
ORDER BY ranking DESC
133133
""", (query, min_score))
134134
return cursor.fetchall()
135135

@@ -161,7 +161,7 @@ SELECT memory_set_option('max_tokens', 512); -- Tokens per chunk
161161
SELECT memory_set_option('overlay_tokens', 100); -- Overlap between chunks
162162

163163
-- Search behavior
164-
SELECT memory_set_option('max_items', 30); -- Max results
164+
SELECT memory_set_option('max_results', 30); -- Max search results
165165
SELECT memory_set_option('min_score', 0.75); -- Score threshold
166166
SELECT memory_set_option('vector_weight', 0.6); -- Vector vs FTS balance
167167
SELECT memory_set_option('text_weight', 0.4);

src/dbmem-search.c

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ SQLITE_EXTENSION_INIT3
3030
#define SEARCH_COLUMN_HASH 3
3131
#define SEARCH_COLUMN_SEQ 4
3232
#define SEARCH_COLUMN_RANKING 5
33+
#define SEARCH_COLUMN_PATH 6
34+
#define SEARCH_COLUMN_SNIPPET 7
3335

3436
typedef struct {
3537
sqlite3_vtab base; // Base class - must be first
@@ -39,7 +41,7 @@ typedef struct {
3941

4042
typedef struct {
4143
sqlite3_vtab_cursor base; // Base class - must be first
42-
int max_items;
44+
int max_results;
4345
bool perform_fts;
4446

4547
int index;
@@ -117,7 +119,7 @@ int vMemorySearchCursorAllocate (vMemorySearchCursor *c, int entries, bool perfo
117119
if (!buffer) return SQLITE_NOMEM;
118120

119121
// adjust all internal pointers
120-
c->max_items = entries;
122+
c->max_results = entries;
121123
c->perform_fts = perform_fts;
122124
c->buffer = buffer;
123125

@@ -162,7 +164,7 @@ int vMemorySearchCursorAllocate (vMemorySearchCursor *c, int entries, bool perfo
162164
return SQLITE_OK;
163165
}
164166

165-
int vMemorySearchCursorMerge(vMemorySearchCursor *c, double vectorWeight, double textWeight, double min_score, int max_items) {
167+
int vMemorySearchCursorMerge(vMemorySearchCursor *c, double vectorWeight, double textWeight, double min_score, int max_results) {
166168
c->merge.count = 0;
167169

168170
// add semantic (vector) results
@@ -244,7 +246,7 @@ int vMemorySearchCursorMerge(vMemorySearchCursor *c, double vectorWeight, double
244246

245247
// copy top results to final cursor arrays, filtering by min_score
246248
c->count = 0;
247-
for (int i = 0; i < c->merge.count && c->count < max_items; i++) {
249+
for (int i = 0; i < c->merge.count && c->count < max_results; i++) {
248250
if (c->merge.textScore[i] < min_score) break; // sorted descending, so stop at first below threshold
249251
c->hash[c->count] = c->merge.hash[i];
250252
c->seq[c->count] = c->merge.seq[i];
@@ -430,7 +432,7 @@ static int vMemorySearchConnect (sqlite3 *db, void *pAux, int argc, const char *
430432
}
431433

432434
// https://www.sqlite.org/vtab.html#table_valued_functions
433-
int rc = sqlite3_declare_vtab(db, "CREATE TABLE x(query hidden, max_entries hidden, context hidden, hash, seq, ranking);");
435+
int rc = sqlite3_declare_vtab(db, "CREATE TABLE x(query hidden, max_entries hidden, context hidden, hash, seq, ranking, path, snippet);");
434436
if (rc != SQLITE_OK) return rc;
435437

436438
vMemorySearchTable *vtab = (vMemorySearchTable *)dbmem_zeroalloc(sizeof(vMemorySearchTable));
@@ -506,20 +508,37 @@ static int vMemorySearchCursorEof (sqlite3_vtab_cursor *cur){
506508
}
507509

508510
static int vMemorySearchCursorColumn (sqlite3_vtab_cursor *cur, sqlite3_context *context, int iCol) {
511+
static const char *path_sql = "SELECT path FROM dbmem_content WHERE hash = ?1;";
512+
static const char *snippet_sql = "SELECT substr(c.value, v.offset + 1, v.length) FROM dbmem_vault v JOIN dbmem_content c ON v.hash = c.hash WHERE v.hash = ?1 AND v.seq = ?2;";
513+
509514
vMemorySearchCursor *c = (vMemorySearchCursor *)cur;
515+
sqlite3 *db = ((vMemorySearchTable *)cur->pVtab)->db;
510516

511517
switch (iCol) {
512518
case SEARCH_COLUMN_HASH:
513-
sqlite3_result_int64(context, (sqlite3_int64)c->hash[c->index]);
519+
sqlite3_result_int64(context, c->hash[c->index]);
514520
break;
515521

516522
case SEARCH_COLUMN_SEQ:
517-
sqlite3_result_int64(context, (sqlite3_int64)c->seq[c->index]);
523+
sqlite3_result_int64(context, c->seq[c->index]);
518524
break;
519525

520526
case SEARCH_COLUMN_RANKING:
521527
sqlite3_result_double(context, c->rank[c->index]);
522528
break;
529+
530+
case SEARCH_COLUMN_PATH:
531+
case SEARCH_COLUMN_SNIPPET:{
532+
const char *sql = (iCol == SEARCH_COLUMN_PATH) ? path_sql : snippet_sql;
533+
sqlite3_stmt *vm = NULL;
534+
if (sqlite3_prepare_v2(db, sql, -1, &vm, NULL) == SQLITE_OK) {
535+
sqlite3_bind_int64(vm, 1, c->hash[c->index]);
536+
if (iCol == SEARCH_COLUMN_SNIPPET) sqlite3_bind_int64(vm, 2, c->seq[c->index]);
537+
if (sqlite3_step(vm) == SQLITE_ROW) sqlite3_result_value(context, sqlite3_column_value(vm, 0));
538+
}
539+
if (vm) sqlite3_finalize(vm);
540+
}
541+
break;
523542
}
524543

525544
return SQLITE_OK;
@@ -540,7 +559,7 @@ static int vMemorySearchCursorFilter (sqlite3_vtab_cursor *cur, int idxNum, cons
540559
sqlite3 *db = searchTab->db;
541560

542561
// check and retrieve arguments
543-
int max_items = dbmem_context_max_items(ctx);
562+
int max_results = dbmem_context_max_results(ctx);
544563
bool perform_fts = dbmem_context_perform_fts(ctx);
545564
const char *query = NULL;
546565
const char *context = NULL;
@@ -559,14 +578,14 @@ static int vMemorySearchCursorFilter (sqlite3_vtab_cursor *cur, int idxNum, cons
559578
if (argc > 1) {
560579
// only the next two arguments are handled
561580
for (int i=1; i<argc && i<=2; ++i) {
562-
if (sqlite3_value_type(argv[i]) == SQLITE_INTEGER) max_items = sqlite3_value_int(argv[i]);
581+
if (sqlite3_value_type(argv[i]) == SQLITE_INTEGER) max_results = sqlite3_value_int(argv[i]);
563582
else if (sqlite3_value_type(argv[i]) == SQLITE_TEXT) context = (const char *)sqlite3_value_text(argv[i]);
564583
// ignore any other type
565584
}
566585
}
567586

568587
// allocate internal cursor buffer
569-
int rc = vMemorySearchCursorAllocate(c, max_items, perform_fts);
588+
int rc = vMemorySearchCursorAllocate(c, max_results, perform_fts);
570589
if (rc != SQLITE_OK) return SQLITE_NOMEM;
571590

572591
// perform semantic search
@@ -608,7 +627,7 @@ static int vMemorySearchCursorFilter (sqlite3_vtab_cursor *cur, int idxNum, cons
608627
}
609628

610629
// perform search
611-
rc = dbmem_semantic_search(db, c, result.embedding, (int)(result.n_embd * sizeof(float)), context, max_items);
630+
rc = dbmem_semantic_search(db, c, result.embedding, (int)(result.n_embd * sizeof(float)), context, max_results);
612631
if (rc != 0) {
613632
sqlvTab->zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(db));
614633
return SQLITE_ERROR;
@@ -617,7 +636,7 @@ static int vMemorySearchCursorFilter (sqlite3_vtab_cursor *cur, int idxNum, cons
617636
// perform fts search
618637
if (perform_fts) {
619638
// in case of FTS error ignore its contribution
620-
rc = dbmem_fts_search(db, c, query, context, max_items);
639+
rc = dbmem_fts_search(db, c, query, context, max_results);
621640
if (rc != SQLITE_OK) perform_fts = false;
622641
}
623642

@@ -632,7 +651,7 @@ static int vMemorySearchCursorFilter (sqlite3_vtab_cursor *cur, int idxNum, cons
632651
double vectorWeight = dbmem_context_vector_weight(ctx);
633652
double textWeight = dbmem_context_text_weight(ctx);
634653
double min_score = dbmem_context_min_score(ctx);
635-
vMemorySearchCursorMerge(c, vectorWeight, textWeight, min_score, max_items);
654+
vMemorySearchCursorMerge(c, vectorWeight, textWeight, min_score, max_results);
636655

637656
// update last_accessed timestamps for returned results
638657
if (dbmem_context_update_access(ctx) && c->count > 0) {

src/sqlite-memory.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ SQLITE_EXTENSION_INIT1
5050
#define DBMEM_SETTINGS_KEY_SKIP_HTML "skip_html"
5151
#define DBMEM_SETTINGS_KEY_EXTENSIONS "extensions"
5252
#define DBMEM_SETTINGS_KEY_ENGINE_WARMUP "engine_warmup"
53-
#define DBMEM_SETTINGS_KEY_MAX_ITEMS "max_items"
53+
#define DBMEM_SETTINGS_KEY_MAX_RESULTS "max_results"
5454
#define DBMEM_SETTINGS_KEY_FTS_ENABLED "fts_enabled"
5555
#define DBMEM_SETTINGS_KEY_VECTOR_WEIGHT "vector_weight"
5656
#define DBMEM_SETTINGS_KEY_TEXT_WEIGHT "text_weight"
@@ -62,7 +62,7 @@ SQLITE_EXTENSION_INIT1
6262
#define DEFAULT_MAX_TOKENS 400
6363
#define DEFAULT_OVERLAY_TOKENS 80
6464
#define DEFAULT_MAX_SNIPPET_CHARS 700
65-
#define DEFAULT_MAX_ITEMS 20
65+
#define DEFAULT_MAX_RESULTS 20
6666
#define DEFAULT_VECTOR_WEIGHT 0.5
6767
#define DEFAULT_TEXT_WEIGHT 0.5
6868
#define DEFAULT_MIN_SCORE 0.7
@@ -99,7 +99,7 @@ struct dbmem_context {
9999
bool dimension_saved; // Embedding dimension needs to be automatically serialized
100100

101101
// Settings
102-
int max_items; // Maximum number of items to be returned from a search
102+
int max_results; // Maximum number of results to be returned from a search
103103
double vector_weight; // Weight of the vector results during the merge of the result
104104
double text_weight; // Weight of the FTS results during the merge of the result
105105
double min_score; // Minimum score threshold to filter irrelevant results
@@ -212,9 +212,9 @@ static int dbmem_settings_sync (dbmem_context *ctx, const char *key, sqlite3_val
212212
return 0;
213213
}
214214

215-
if (strcasecmp(key, DBMEM_SETTINGS_KEY_MAX_ITEMS) == 0) {
215+
if (strcasecmp(key, DBMEM_SETTINGS_KEY_MAX_RESULTS) == 0) {
216216
int n = sqlite3_value_int(value);
217-
if (n > 0) ctx->max_items = n;
217+
if (n > 0) ctx->max_results = n;
218218
return 0;
219219
}
220220

@@ -470,7 +470,7 @@ static void *dbmem_context_create (sqlite3 *db) {
470470
ctx->engine_warmup = false;
471471

472472
ctx->perform_fts = fts5_is_available;
473-
ctx->max_items = DEFAULT_MAX_ITEMS;
473+
ctx->max_results = DEFAULT_MAX_RESULTS;
474474
ctx->vector_weight = DEFAULT_VECTOR_WEIGHT;
475475
ctx->text_weight = DEFAULT_TEXT_WEIGHT;
476476
ctx->min_score = DEFAULT_MIN_SCORE;
@@ -567,8 +567,8 @@ bool dbmem_context_perform_fts (dbmem_context *ctx) {
567567
return ctx->perform_fts;
568568
}
569569

570-
int dbmem_context_max_items (dbmem_context *ctx) {
571-
return ctx->max_items;
570+
int dbmem_context_max_results (dbmem_context *ctx) {
571+
return ctx->max_results;
572572
}
573573

574574
double dbmem_context_vector_weight (dbmem_context *ctx) {

src/sqlite-memory.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
extern "C" {
2727
#endif
2828

29-
#define SQLITE_DBMEMORY_VERSION "0.5.1"
29+
#define SQLITE_DBMEMORY_VERSION "0.5.2"
3030

3131
// public API
3232
SQLITE_DBMEMORY_API int sqlite3_memory_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi);
@@ -38,7 +38,7 @@ void *dbmem_context_engine (dbmem_context *ctx, bool *is_local);
3838
bool dbmem_context_load_vector (dbmem_context *ctx);
3939
bool dbmem_context_load_sync (dbmem_context *ctx);
4040
bool dbmem_context_perform_fts (dbmem_context *ctx);
41-
int dbmem_context_max_items (dbmem_context *ctx);
41+
int dbmem_context_max_results (dbmem_context *ctx);
4242
double dbmem_context_vector_weight (dbmem_context *ctx);
4343
double dbmem_context_text_weight (dbmem_context *ctx);
4444
double dbmem_context_min_score (dbmem_context *ctx);

0 commit comments

Comments
 (0)