Skip to content

Commit 59ee9ed

Browse files
authored
feat: support empty directory markers (#11)
Add explicit empty directory markers via memory_add_content('dirname/', '') for path-preserving virtual filesystem workflows. Directory marker creation now requires preserve_duplicate_paths=1, stores marker paths with a trailing slash, rejects file/directory conflicts, and keeps markers out of search indexes. Update listing, delete, rename, materialize, directory sync cleanup, and reindex paths to handle markers consistently. Document the new behavior and add focused unit coverage for marker creation, conflicts, listing, deletion, materialization, and reindex survival.
1 parent 6b1dfca commit 59ee9ed

4 files changed

Lines changed: 636 additions & 20 deletions

File tree

API.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,11 +308,16 @@ Indexes caller-provided file content without reading from the filesystem.
308308
- If the path was previously indexed with different content, the old entry (chunks, embeddings, FTS) is deleted and new content is reindexed
309309
- If the new content is already indexed under another path, the stale path is removed and the existing content entry is reused
310310
- Set `preserve_duplicate_paths=1` to preserve separate rows for distinct paths with identical or empty content
311+
- With `preserve_duplicate_paths=1`, an empty `content` value and a trailing slash in `path` creates an explicit empty directory marker, for example `memory_add_content('dirname/', '')`
312+
- Directory markers are stored in `dbmem_content` with a trailing slash path, are shown as directories by `memory_list_files()`, and are not indexed for search
313+
- Directory marker paths cannot contain non-empty content and cannot conflict with a file path of the same name
311314
- Available even when compiled with `DBMEM_OMIT_IO`
312315

313316
**Example:**
314317
```sql
315318
SELECT memory_add_content('docs/api.md', '# API\nContent already loaded by the caller.', 'documentation');
319+
SELECT memory_set_option('preserve_duplicate_paths', 1);
320+
SELECT memory_add_content('docs/drafts/', '');
316321
```
317322

318323
---
@@ -334,6 +339,7 @@ Renames an indexed file path in memory without reprocessing content.
334339
- It does not rename the file on disk or change the stored `source_path` value
335340
- Does not change `hash`, `value`, embeddings, or FTS entries
336341
- Fails if `new_path` already exists because `dbmem_content.path` is unique
342+
- Explicit directory markers can be renamed only to another trailing-slash marker path; this renames only the marker row, not child paths
337343
- Fails if `old_path` matches more than one row across `path` and local `dbmem_content_source.source_path`; pass a unique logical path or exact local source path
338344

339345
**Example:**
@@ -392,10 +398,11 @@ Writes all stored file contents from `dbmem_content` back to the filesystem.
392398
|-----------|------|----------|-------------|
393399
| `root_path` | TEXT | No | Filesystem root used to materialize relative paths |
394400

395-
**Returns:** INTEGER - Number of files processed
401+
**Returns:** INTEGER - Number of files or explicit directory markers processed
396402

397403
**Notes:**
398404
- Creates parent directories as needed
405+
- Explicit directory markers created with `memory_add_content('dirname/', '')` are materialized as directories
399406
- Relative paths are written under `root_path` when provided
400407
- Paths containing `..` segments are rejected to prevent writing outside the materialization root
401408
- If a file already exists with the same content, it is left unchanged and no error is returned
@@ -421,7 +428,7 @@ Returns a JSON tree with the indexed directories and files stored in `dbmem_cont
421428
**Notes:**
422429
- Rows added with `memory_add_text()` use generated paths and can appear as root-level file nodes
423430
- Legacy absolute paths are displayed with their common directory prefix removed when possible
424-
- Directory nodes are derived from indexed file paths
431+
- Directory nodes are derived from indexed file paths and explicit directory markers
425432
- Path separators are normalized to `/` in the returned JSON
426433
- Sibling nodes are sorted with directories first, then files; each group is alphabetical
427434

@@ -488,7 +495,7 @@ SELECT memory_delete_context('meetings');
488495

489496
#### `memory_delete_file(path TEXT)`
490497

491-
Deletes an indexed file by its stored path.
498+
Deletes an indexed file or explicit directory marker by its stored path.
492499

493500
**Parameters:**
494501
| Parameter | Type | Description |
@@ -500,6 +507,8 @@ Deletes an indexed file by its stored path.
500507
**Notes:**
501508
- Atomically deletes the matching `dbmem_content` entry and its rows in `dbmem_vault` and `dbmem_vault_fts`
502509
- Does not delete or modify the file on disk
510+
- When the path names an explicit directory marker, deletes only that marker row; files under the directory are not deleted
511+
- Directory markers can be matched with either `dirname` or `dirname/`
503512
- Path matching is exact; if a row has local source metadata, `dbmem_content_source.source_path` is also accepted
504513
- Fails if the argument matches more than one row across `path` and local `dbmem_content_source.source_path`
505514

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,15 @@ SELECT memory_set_option('preserve_duplicate_paths', 1);
232232

233233
In this mode, `dbmem_content.hash` identifies the stored entry and is scoped by path.
234234

235+
The same mode supports explicit empty directory markers for virtual filesystems:
236+
237+
```sql
238+
SELECT memory_set_option('preserve_duplicate_paths', 1);
239+
SELECT memory_add_content('dirname/', '');
240+
```
241+
242+
Directory markers are listed as directories, materialized as directories by `memory_materialize_files()`, and ignored by `memory_search`.
243+
235244
`memory_add_text()`, `memory_add_file()`, and `memory_add_content()` each run inside a SQLite SAVEPOINT transaction. `memory_add_directory()` performs its cleanup pass transactionally and then processes each file in its own transaction. If one file fails, that file rolls back cleanly and previously-committed files remain valid; there are no partially-indexed rows or orphaned chunk/FTS entries for the failed file.
236245

237246
This makes all sync functions safe to call repeatedly - for example, on a cron schedule or at agent startup - with minimal overhead.

0 commit comments

Comments
 (0)