Changelog
[1.5.7dev1] - 2026-05-24
Performance Improvements
- PERF-30: Skip unnecessary old-value lookup for hook-free
__setitem__(core.py)old_valueforon_write_successis now fetched only when hooks are registered.- Default single-key writes avoid an extra cache/DB lookup.
- PERF-31 / PERF-32: Lighten the
batch_get()bulk-read hot path (core.py)- Missing-key detection now tracks only keys queried from SQLite instead of building a set from the full result.
- Full-chunk
IN (...)placeholders are precomputed to reduce string construction during large batch reads.
- PERF-33: Speed up fully cache-known
AsyncNanaSQLite.abatch_get()(async_core.py)- With the default unbounded cache and no hooks, async batch reads now return without an executor round trip when every requested key is cached or known absent.
- PERF-34: Bulk-populate the default cache in
load_all()(core.py)- For unlimited unbounded caches,
load_all()now updates the backing dict directly instead of callingcache.set()for every row.
- For unlimited unbounded caches,
- PERF-35: Add
memory_first=True(core.py)- KVS CRUD now has an explicit memory-first option that serves loaded data from memory and persists deltas through the v2 engine's timed background flush.
- The default interval is equivalent to
flush_interval=5.0, andclose()performs the v2 engine's final flush. - This is opt-in and does not affect existing behavior. Because it requires full in-memory ownership, it rejects LRU/TTL and bounded caches.
- The v2 engine's timed flush now skips submitting empty flush jobs when there are no changes.
- Added memory-first CRUD benchmarks to pytest-benchmark and the Actions benchmark summary.
[1.5.6b1] - 2026-05-24
Security Fixes
- SEC-01: Hardened subquery rejection for
query(..., columns=[...])(core.py)- Column expressions now reject subquery keywords such as
SELECT/FROMin strict mode, matching the existing ORDER BY / GROUP BY hardening. Normal aggregate expressions such asCOUNT(*) AS totalremain supported.
- Column expressions now reject subquery keywords such as
- SEC-02: Rejected top-level comma injection in
create_table()column types (core.py)- Commas remain allowed inside balanced parentheses, such as
DECIMAL(10,2), while definitions such asTEXT, injected INTEGERare rejected.
- Commas remain allowed inside balanced parentheses, such as
- BUG-02: Bounded V2 Dead Letter Queue (DLQ) growth (
v2_engine.py)- Added a default DLQ limit of
1000; when full, the oldest entry is evicted so newer failures remain visible. Tune viaV2Config(max_dlq_size=...)orv2_max_dlq_size=...; passNoneto keep the previous unbounded behavior.
- Added a default DLQ limit of
- SEC-03: Rejected unsafe table names in
V2EngineKVS / DLQ recovery paths (v2_engine.py)- Direct
V2Enginecallers now get table-name validation inkvs_set()/kvs_delete()/kvs_get_staging()and DLQ recovery, preventing unsafe names from being interpolated into SQL fragments.
- Direct
- SEC-04: Restricted writable PRAGMAs in
pragma()(core.py)- Informational or dangerous PRAGMAs such as
schema_versionandtable_inforemain readable but can no longer be set through NanaSQLite.
- Informational or dangerous PRAGMAs such as
Bug Fixes
- QUAL-01: Fixed missing
lock_timeoutforwarding inAsyncNanaSQLite(async_core.py)AsyncNanaSQLite(..., lock_timeout=...)did not forward the value to the internalNanaSQLiteinstance, effectively disabling lock acquisition timeouts for the async API.- Added regression coverage confirming that sub-tables created by
AsyncNanaSQLite.table()inherit the parent'slock_timeout.
Performance Improvements
- PERF-01: Faster cached hot paths for
AsyncNanaSQLite.aget()/acontains()(async_core.py)- In the default unbounded cache mode, cached keys and known-absent keys now return without an executor round-trip. Cache misses, LRU/TTL modes, and hook-backed reads continue to delegate to the synchronous DB path.
- Added async benchmark cases for cached reads and known-absent reads.
Documentation
- Documented
lock_timeoutin the async API references and guides.
[1.5.5] - 2026-04-30
Security Remediation
- F-002: Resolved State Inconsistency in UniqueHook (
core.py,hooks.py,protocols.py)- Fixed a race where in-memory indices were updated even if the database write failed. Introduced
on_write_successandon_delete_successcallbacks to theNanaHookprotocol to ensure indices are only updated after successful DB commitment.
- Fixed a race where in-memory indices were updated even if the database write failed. Introduced
- F-003: Hardened SQL Injection Protection for ORDER BY / GROUP BY (
core.py)- Implemented strict whitelist validation (allowing only alphanumeric, underscores, dots, commas, and spaces) for clauses where parameter binding is not supported by SQLite.
- F-004: Prevented Information Exposure in Dead Letter Queue (DLQ) (
v2_engine.py)- Verified and hardened logging to ensure sensitive payloads are not leaked in error logs during background flush failures. Added security warnings to DLQ-related documentation.
- F-005: Fixed Race Condition in ExpiringDict (TTL Cache) (
utils.py)- Resolved a race where a key refreshed during the eviction process could be erroneously purged. Implemented a Compare-and-Delete (CAS) pattern that verifies the expiry timestamp before performing deletion.
Documentation Improvements
- Clarified F-001 (atexit) Limitations (
README.md,v2_architecture.md)- Documented the risk of data loss during forced process termination (
SIGKILL) in v2 mode. Recommendedflush(wait=True)orimmediatemode for mission-critical data.
- Documented the risk of data loss during forced process termination (
[1.5.4] - 2026-04-19
Bug Fixes
BUG-01 (pop hook lock): Move
before_deletehook call inside lock inpop()(core.py)- In non-v2 mode,
before_deletehooks inpop()were called outside the lock, breaking consistency with the SEC-05 fix applied to__delitem__. The hook call and the DB delete now run atomically under_acquire_lock().self._lockis athreading.RLock, so reentrant calls from hooks do not deadlock. - If a hook raises, the DB deletion is skipped and the key is retained.
- In non-v2 mode,
BUG-02 (batch_update hook result): Always apply hook-returned values in
batch_update()(core.py)- The non-coerce branch of
batch_update()silently discarded the return value ofbefore_writehooks, causing transforming hooks (e.g.PydanticHook, custom hooks) to work through__setitem__but be silently ignored inbatch_update(). - Removed the
if self._coerce:two-branch structure in favour of a unified copy-on-write pattern that always applies hook transformations. A new dict is only allocated when at least one hook changes a value.ValidkitHookinternally controls whether to transform based on its owncoercesetting, preserving backward compatibility.
- The non-coerce branch of
BUG-03 (batch_delete hook lock): Move
before_deletehook call inside lock inbatch_delete()(core.py)- In non-v2 mode,
before_deletehooks inbatch_delete()were called outside the lock, inconsistent with the fixed__delitem__. Hooks now run inside the lock in non-v2 mode; v2 mode continues to run hooks outside the lock (consistent with__delitem__v2 path).
- In non-v2 mode,
Security Fixes
- SEC-05: Fixed TOCTOU race condition in
UniqueHook(core.py,hooks.py)before_writehooks in__setitem__andbefore_deletehooks in__delitem__were called outside the_acquire_lock()context, allowing a race window where two concurrent threads could both pass the uniqueness check and write duplicate values, or a pre-delete consistency check could be violated by a concurrent operation.- In non-v2 mode, both the
before_writeandbefore_deleteinvocations are now inside the_acquire_lock()block, making the hook check and the DB write/delete atomic. Sinceself._lockis athreading.RLock, reentrant calls from hooks (e.g.,db.items()) do not deadlock. - Updated
UniqueHookdocstring: removed the oldWARNINGand described the fix (SEC-03 → SEC-05). - v2 mode is unaffected (asynchronous flush architecture); use SQLite UNIQUE constraints for strict uniqueness in v2 mode.
Security Enhancement
- SEC-06 opt-in:
google-re2ReDoS protection (compat.py,hooks.py,pyproject.toml)- Installing
pip install nanasqlite[re2]enables the RE2 engine for all regex compilation and matching inBaseHook. RE2 guarantees linear-time execution for any input, making ReDoS attacks impossible. - A
logging.debugmessage is emitted at import time when RE2 is active (nanasqlite.compatlogger). - Without RE2, the existing dangerous-pattern blacklist validation continues to function.
- Added
re2 = ["google-re2>=1.1"]optional dependency topyproject.tomland included it inall. - Added
google-re2>=1.1todevextras so that CI tests run with the real RE2 engine. - Updated error message in
_validate_regex_patternto suggestpip install nanasqlite[re2]. - Added
re_fallbackparameter toBaseHook: controls fallback behaviour when RE2 rejects a pattern (e.g. backreferences(\w)\1, lookarounds(?=...)).re_fallback=False(default): propagatesre2.Errorunchanged; ReDoS protection is fully maintained.re_fallback=True: emitswarnings.warnand falls back to the standardreengine; ReDoS protection is disabled for that pattern.
- Installing
Performance Improvements (accelerated)
PERF-01:
UniqueHook— opt-in inverse index (use_index=True) (hooks.py)- The default
before_writeperformed an O(N) full scan viadb.items()on every write, becoming a severe bottleneck for large tables. - Pass
use_index=Trueto enable a lazy-built inverse index ({field_value → key}): the index is constructed once on the first write (O(N)) and subsequent uniqueness checks are O(1). - The index is kept up-to-date automatically through
before_writeandbefore_deletecallbacks. Callhook.invalidate_index()after any out-of-lifecycle DB modifications (e.g.db.execute()). - Backward-compatible:
use_index=False(default) preserves the original O(N) behaviour.
- The default
PERF-02:
BaseHook.__init__— skip recompilation for already-compiledPattern(hooks.py)- In non-RE2 mode, passing an already-compiled
re.Patternobject no longer triggersre.compile()again. The compiledPatternobject is used directly, reducing hook initialization overhead. - The
_validate_regex_patterncheck onpattern.patternis still executed for security (ensuring compiled patterns cannot bypass the dangerous-pattern blacklist). - Safety is preserved while reducing unnecessary recompilation overhead.
- In non-RE2 mode, passing an already-compiled
Security Enhancement (accelerated)
- SEC-01: Document DLQ payload exposure risk (
v2_engine.py)- DLQ entries contain serialised KVS values (
op["value"]); for unencrypted databases,get_dlq()exposes plaintext data to any consumer of the returned list. - Added a SEC-01 security notice to
DLQEntry,_add_to_dlq(), andget_dlq()docstrings, advising callers to log onlyerror_msg/timestampin production and to handleitemin a trusted context only.
- DLQ entries contain serialised KVS values (
Code Quality (accelerated)
QUAL-01:
compat.py— proper type annotation forre2_module(compat.py)- Changed
re2_module = None # type: ignore[assignment]tore2_module: types.ModuleType | None = None, eliminating thetype: ignoreescape and allowing mypy to track the type at all usage sites.
- Changed
QUAL-02:
v2_engine.py— introduceDLQEntrydataclass (v2_engine.py)- Replaced the untyped
list[tuple[str, Any, float]]DLQ storage withlist[DLQEntry], whereDLQEntryis a typed@dataclasswith fieldserror_msg,item, andtimestamp.get_dlq()still returnslist[dict]for backward compatibility.
- Replaced the untyped
Code Quality
- QUAL-10:
compat.py— replacevalidkit_validate = Nonewith a stub function (compat.py)- When
validkit-pyis not installed,validkit_validate = Nonecaused a confusingTypeError: 'NoneType' object is not callable. Changed to a stub that raisesImportErrorwith a clear installation message.
- When
[1.5.3rc3] - 2026-04-07
Performance Improvements
PERF-21:
execute_many()— Python loop replaced withcursor.executemany()(core.py)execute_many()was iteratingparameters_listwith aforloop callingcursor.execute()per item. Switching to APSW's built-incursor.executemany()eliminates per-parameter Python call overhead. ~15% improvement fortest_execute_manyandtest_import_from_dict_list.
PERF-22:
batch_delete()— skip pre-check loop when no hooks are registered (core.py)batch_delete()iterated all keys calling_ensure_cached()before deletion. Its only purpose was firingbefore_deletehooks. When_has_hooksisFalse(the default), this loop is now skipped entirely, saving O(n) function-call overhead per batch.
PERF-23:
batch_update()— serialize outside lock,dict.update(),_absent_keysguard (core.py)- Serialization (
_serialize()) is pure Python/JSON work that does not touch the SQLite connection; moved it outside the lock (consistent with__setitem__). - Unbounded-mode cache update changed from a per-key assignment loop to
dict.update(), which is ~6× faster (C-level implementation). _absent_keys.discard()per-key calls replaced withif self._absent_keys: self._absent_keys.difference_update(mapping.keys()), eliminating all hash operations when the set is empty (the common write-heavy case).- Same improvements applied to the v2 path.
- ~9% overall improvement for
test_batch_write_100.
- Serialization (
PERF-24:
batch_update_partial()—dict.update()+_absent_keysguard (core.py)- Same optimizations as PERF-23 applied to both v1 and v2 paths of
batch_update_partial().
- Same optimizations as PERF-23 applied to both v1 and v2 paths of
PERF-25:
batch_delete()— per-key_absent_keys.add()→_absent_keys.update(keys)(core.py)- After deletion, all absent keys are now added to
_absent_keyswith a singleupdate(keys)call instead of individualadd(key)calls in a loop, reducing hash-computation overhead. - Same change applied to the v2 path.
- After deletion, all absent keys are now added to
PERF-26:
begin_transaction()/commit()/rollback()— bypassexecute()overhead (core.py)- These methods previously called
self.execute("BEGIN IMMEDIATE")etc., routing through the fullexecute()dispatch (v2-mode check,strip().upper(), duplicate_check_connection()call). They now callself._connection.execute()directly under the acquired lock, saving several redundant operations per transaction. Improvement observed intest_context_manager_transaction,test_begin_commit, andtest_begin_rollback.
- These methods previously called
PERF-29:
_serialize()— early return for no-encryption case (core.py)- A
_no_encrypt: boolflag is pre-computed in__init__(same pattern as PERF-20's_has_hooks). When encryption is disabled (the default),_serialize()now returns immediately after JSON encoding without evaluating theif self._fernet:andif self._aead:attribute-lookup branches. Small but consistent improvement across all write paths.
- A
[1.5.3rc2] - 2026-04-07
Bug Fixes
- [Medium] BUG-01:
setdefault()returns wrong value whenbefore_writehook transforms the default (core.py)- The PERF-18 optimisation applied
after_readhooks to the originaldefaultargument rather than the potentially hook-transformed value actually stored in cache. For example, withValidkitHook(coerce=True)orPydanticHookthat transforms values on write,setdefault("k", "hello")would store"HELLO"but return"hello". - Fix: When
_has_hooksis True, the new code reads the stored (potentially transformed) value from_data/cache after the write and appliesafter_readhooks to that. When_has_hooksis False no hooks can transform values, so returningdefaultdirectly (the PERF-18 fast path) remains valid. - POC:
etc/poc/poc_bug01_setdefault_coerce_hook.py
- The PERF-18 optimisation applied
Performance Fixes (v1.5.3rc2 benchmark regression fix)
[High] PERF-14: try/except fast path for
__getitem__in Unbounded mode (core.py)- The cache-hit hot path used
.get(key, _NOT_FOUND)plus a sentinel identity check. Direct dict accessd[key]viatry/except KeyErroreliminates sentinel creation, keyword-argument processing, and the identity compare — approximately 1.9x faster for the common cache-hit case. Applied to_ensure_cached(),__getitem__, andget(). Measured -15% improvement ontest_single_read_cached.
- The cache-hit hot path used
[High] PERF-15: try/except fast path for
get()in Unbounded mode (core.py)- Same optimisation as PERF-14 applied to
get(). Measured -11.6% improvement ontest_read_encryption[fernet].
- Same optimisation as PERF-14 applied to
[High] PERF-16: try/except fast path for
__contains__in Unbounded mode (core.py)- Same optimisation as PERF-14 applied to
__contains__.
- Same optimisation as PERF-14 applied to
[Medium] PERF-17: Guard empty
_absent_keys.discard()in_update_cache()(core.py)- Since v1.5.2's
_absent_keysintroduction,_update_cache()calledself._absent_keys.discard(key)on every write — even when the set was empty (common in write-heavy workloads). Added anif self._absent_keys:guard so the hash computation is skipped when unnecessary.
- Since v1.5.2's
[Medium] PERF-18: Eliminate redundant
self[key]round-trip insetdefault()(core.py)- After writing a new default,
setdefault()re-read the value viaself[key](a full__getitem__call). Since the value is already known, it is now returned directly. Also switched Unbounded mode to use_data[key]instead of the polymorphic_cache.get().
- After writing a new default,
[Medium] PERF-19: Direct
_dataaccess inpop()for Unbounded mode (core.py)pop()retrieved the value viaself._cache.get(key). In Unbounded mode_dataholds the real value; usingself._data[key]avoids the polymorphic method dispatch (andmove_to_end()overhead in LRU mode).
[Medium] PERF-20: Pre-computed
_has_hooksflag to speed up all hot paths (core.py)- All hot paths (
__getitem__,__setitem__,__delitem__,get(),get_fresh(),batch_get(),pop(),setdefault(),batch_update_partial(),batch_delete()) were callingif self._hooks:which triggerslist.__len__on every operation. Aself._has_hooks: boolflag is now pre-computed at__init__time and kept in sync byadd_hook().
- All hot paths (
Tests
- Extended
tests/test_v153_perf_fixes.pyto cover PERF-14 through PERF-20 correctness and BUG-01 regression tests.
[1.5.3rc1] - 2026-04-07
Performance Fixes (v1.5.3 pre-release audit)
[High] PERF-07: Pre-compute common SQL strings at
__init__time (core.py)- Hot paths (
__setitem__,__delitem__,__contains__,__len__,_write_to_db,_read_from_db,_delete_from_db,load_all,batch_update,batch_delete) were re-building the same SQL strings (containing the quoted table name) via f-strings on every call. Six SQL template strings are now pre-computed at instance creation time and referenced directly on the hot paths. - Impact: Eliminates string-building overhead on every KV operation. Improves
test_single_write/test_execute_raw/test_sql_insert_single.
- Hot paths (
[Medium] PERF-08: Skip MISSING sentinel filter in
to_dict()/copy()for Unbounded mode (core.py)- In Unbounded mode
_datanever holds the MISSING sentinel, so the per-elementif v is not MISSINGpredicate in the dict comprehension was unnecessary overhead. Unbounded mode now returnsdict(self._data)directly; LRU/TTL mode still applies the filter. - Impact: Reduces overhead for
test_to_dict_1000/test_copy.
- In Unbounded mode
[Medium] PERF-09: Eliminate double LRU cache lookup in
__getitem__(core.py)- In LRU/TTL mode,
__getitem__called_ensure_cached()(which internally callsself._cache.get(), invokingmove_to_end()) and then calledself._cache.get()a second time to retrieve the value — twomove_to_end()calls for a single cache hit. Restructured to check_datamembership first and callself._cache.get()exactly once for the cache-hit path. - Impact: Removes redundant
move_to_end()on LRU cache hits. Improvestest_cache_hit[lru]/test_cache_hit[ttl].
- In LRU/TTL mode,
[Medium] PERF-10: Pre-compiled regex and early skip for
_validate_expression()(core.py)_validate_expression()ran four separatere.search()calls with individual pattern strings on every invocation. Combined into a single module-level pre-compiled regex_DANGEROUS_SQL_RE. Also added an early return when the expression contains no(, skipping the expensivesanitize_sql_for_function_scan()+re.findall()that is only needed when function calls are present.- Impact: Reduces regex overhead for typical parameterised WHERE clauses. Improves
test_sql_update_single/test_exists_check/test_execute_raw.
[Medium] PERF-11: Lock-free fast-path in
ExpiringDict._check_expiry()(utils.py)_check_expiry()always acquiredthreading.RLockeven for keys that were clearly not expired. Under CPython's GIL, individualdict.get()is atomic, so an optimistic lock-free pre-check against_exptimesis safe. When the expiry time has not been reached the method now returnsFalseimmediately without touching the lock.- Impact: Reduces lock-acquire overhead on TTL cache hit path. Improves
test_cache_hit[ttl]/test_ttl_expiry_check.
New Benchmark Tests
- Added
test_cache_hit[lru]/test_cache_hit[ttl]totests/test_benchmark.pyto measure the cache hit overhead for a single key with repeated reads.
Tests
Added
tests/test_v153_perf_fixes.pycovering:- PERF-07: Presence and correctness of
_sql_kv_*pre-computed attributes - PERF-08:
to_dict()/copy()returns no MISSING sentinels in both Unbounded and LRU modes - PERF-09: LRU / TTL
__getitem__returns correct values on cache hit / miss / MISSING sentinel - PERF-10: Simple WHERE clauses, function-bearing WHERE clauses, and dangerous patterns all handled correctly
- PERF-11:
ExpiringDict._check_expiry()behaves correctly before and after expiry
- PERF-07: Presence and correctness of
Added
TestPerf12GetDoubleLookup/TestPerf13ValuesItemsFiltertotests/test_audit_poc.py:- PERF-12: LRU / TTL
get()returns correct values on cache hit / known-absent / miss - PERF-13:
values()/items()contains no MISSING sentinels in both Unbounded and LRU modes
- PERF-12: LRU / TTL
[High] PERF-12: Eliminate double cache lookup in
get()for LRU/TTL mode (core.py) (found by v1.5.3 audit)- When PERF-09 fixed
__getitem__, the same double-lookup issue remained inget(). Theget()method called_ensure_cached()(which callscache.get()→move_to_end()internally) and then calledcache.get()again to retrieve the value. Applied the same_datamembership check + singlecache.get()pattern as PERF-09. - Impact: Eliminates redundant
move_to_end()inget()for LRU/TTL cache hits. Further improvestest_cache_hit[lru]/test_cache_hit[ttl].
- When PERF-09 fixed
[Medium] PERF-13: Skip MISSING sentinel filter in
values()/items()for Unbounded mode (core.py) (found by v1.5.3 audit)- When PERF-08 optimised
to_dict(), the same optimisation was not applied tovalues()anditems(). Unbounded mode now returnslist(_data.values())/list(_data.items())directly without the per-elementif v is not MISSINGpredicate. - Impact: Reduces per-element overhead in
values()anditems()for Unbounded mode.
- When PERF-08 optimised
Note (QUAL-01): In non-strict mode,
_validate_expression()now emits a singleUserWarningeven when multiple dangerous patterns match (e.g.,; DROP TABLE). Previously, one warning was emitted per matching pattern. In strict mode (exception-raising), behaviour is unchanged. Code that relies on the count ofUserWarnings emitted for a single expression should be updated.
[1.5.2] - 2026-04-06
Performance Fixes (Follow-up for regression since v1.5.0dev1)
- [High] PERF-06: Fast-path optimization for Unbounded cache reads (
core.py)- In Unbounded mode, read-heavy paths (
__getitem__,get,__contains__,_ensure_cached) still had metadata checks before looking at_data, adding avoidable membership checks even for positive cache hits. - Changes:
- Prioritize
_datalookup as the primary fast-path for positive cache hits - Use
_absent_keysonly for known-absent early return - Apply the same fast-path pattern in
__getitem__/getto reduce unnecessary_ensure_cached()calls
- Prioritize
- Impact: Reduces overhead on cached read / contains hot paths while preserving existing public behavior.
- In Unbounded mode, read-heavy paths (
Breaking Change (approved)
- In Unbounded mode, internal mixed-state metadata was split from
_cached_keysto_absent_keys(known-absent only).- No public API change, but code depending on internal
_cached_keyssemantics is not compatible. - Migration: use public APIs (
in,get,is_cached) instead of internal metadata fields.
- No public API change, but code depending on internal
Tests
- Added
tests/test_v152_perf_fastpath.pyto verify:_data-first fast-path behavior in Unbounded mode- Preserved negative-cache semantics for known-absent keys
Audit (etc/audit/audit_prompt.md aligned)
- Performed focused audit checks (Phase 1-6 perspective) on the changed scope:
- No backward-incompatible API change
- No new security issue introduced
- Negative-cache semantics preserved
[1.5.1] - 2026-04-05
Security Fixes (v1.5.1 Pre-Release Audit)
[Medium] SEC-01: Apply
_validate_expression()toexists()WHERE clause (core.py)query()/count()/query_with_pagination()all validate the WHERE clause through_validate_expression(), enforcingforbidden_sql_functionsandstrict_sql_validationpolicies.exists()skipped this validation, allowing a forbidden function to be used in its WHERE clause while being rejected by all other query methods — an inconsistency that undermined application-level SQL policy enforcement. Fixed by adding_validate_expression(where, context="where")toexists().
[Medium] SEC-02: Apply
_validate_expression()tosql_update()/sql_delete()WHERE clause (core.py)- Same as SEC-01:
sql_update()andsql_delete()did not validate their WHERE clause, sostrict_sql_validation/forbidden_sql_functionssettings had no effect on these methods. Fixed with_validate_expression(where, context="where")in both methods.
- Same as SEC-01:
Bug Fixes (v1.5.1 Pre-Release Audit)
[High] BUG-01: Fix
pop()bypassing v2 engine staging buffer (core.py)- In v2 mode,
pop()called_delete_from_db()directly instead of routing throughv2_engine.kvs_delete(). If the key had a pending SET in the staging buffer (not yet flushed), the direct DB DELETE was a no-op (key not in DB yet), but the staging SET was left intact. On the nextflush(), the SET was applied to the DB, resurrecting the deleted key. Fixed by routing v2-modepop()throughv2_engine.kvs_delete(), matching__delitem__.
- In v2 mode,
[Medium] BUG-02: Fix
batch_get()ignoring_cached_keys"known absent" status (core.py)- After
__delitem__, the key is recorded as "known absent" in_cached_keysbut removed from_data.get()(via_ensure_cached) correctly honours this and returns the default.batch_get()only checked_data, so a cache miss caused it to fall through to a DB query — in v2 non-immediate mode, the pending delete may not yet be in DB, sobatch_get()would return the stale old value whileget()returned absent. Fixed by checking_cached_keysinbatch_get()'s cache-miss path.
- After
[Low] BUG-03: Fix
to_dict()returningMISSINGsentinel in LRU/TTL mode (core.py)- In LRU/TTL cache mode, lookups for non-existent keys write the
MISSINGsentinel into the cache as a negative entry.to_dict()returneddict(self._data)which included these sentinel values, unlikeitems()which correctly filtered them. Fixed by using a dict comprehension withif v is not MISSING.
- In LRU/TTL cache mode, lookups for non-existent keys write the
Performance Improvements (v1.5.1 Pre-Release Audit)
- [Low] PERF-05: Pre-compute
_SAFE_SQL_CHARSas a module-levelfrozenset(sql_utils.py)fast_validate_sql_chars()re-created aset(...)object on every call. Because this function is called on every_validate_expression()invocation (hot path for all query methods), building the same immutable set repeatedly wasted ~200–300 ns per call. Moved to a module-levelfrozensetconstant_SAFE_SQL_CHARScomputed once at import time.
Performance Fixes (Regression since v1.5.0dev1)
Fixed performance regressions observed in RPI benchmarks that appeared starting from v1.5.0dev1.
[Critical] PERF-01: Remove hook hot-path overhead (
core.py)- All read/write operations (
__getitem__,__setitem__,__delitem__,get,batch_get,setdefault,pop,batch_update_partial,batch_delete) were callinggetattr(self, "_hooks", [])on every invocation, causing measurable overhead even when no hooks are registered. Changed to directself._hooksaccess (always initialized) with anif self._hooks:early-exit guard. - Impact: ~30% throughput improvement for cached reads (RPI: ~1.74M → ~2.3M ops/sec equivalent).
- All read/write operations (
[Critical] PERF-02: Eliminate shared-lock contention in v2 mode (
core.py)- In the v2-mode paths of
__setitem__,__delitem__,batch_update, andbatch_delete, the in-memory cache update (_data[key] = value, etc.) was being wrapped in the same lock used by the background flush thread for database transactions. On slow CPUs (Raspberry Pi and similar ARM devices) this caused severe throughput degradation because the main thread and background flush thread constantly competed for the same lock. - In v2 mode, in-memory-only updates are atomic under Python's GIL, and the background flush thread never accesses
_dataor_cached_keysdirectly, so no explicit lock is required for these operations. - Impact: ~3.7× throughput improvement for v2 immediate-mode writes (RPI: ~169 → ~600+ calls/sec equivalent).
- In the v2-mode paths of
[Medium] PERF-03: Pre-compute
_update_cachedispatch flag (core.py)_update_cache()calledhasattr(self._cache, "_max_size")on every invocation, adding unnecessary overhead on the write hot path. The result is now pre-computed as_use_cache_setduring__init__, eliminating thehasattr()call entirely.- Impact: ~2-3% write throughput improvement.
[Medium] PERF-04: Replace
@contextmanagerwith direct RLock return in_acquire_lock()(core.py)_acquire_lock()used a@contextmanagergenerator, incurringcontextliboverhead (object allocation,next()calls) on every lock acquisition. For the common case (no timeout), the method now returnsself._lock(athreading.RLock) directly —RLockis itself a context manager with highly-optimised C-level__enter__/__exit__. Whenlock_timeoutis set, a lightweight_TimedLockContexthelper is returned instead.- Impact: ~7% write throughput improvement for non-v2 paths.
[1.5.0] - 2026-04-04
Security Fixes (v1.5.0 Pre-Release Audit)
- [Critical] SEC-03: Documented and added warnings for the TOCTOU (Time-of-check/Time-of-use) race condition in
UniqueHook. The uniqueness check occurs outside the database write transaction, meaning multiple threads can bypass the constraint in concurrent environments. Class docstring now clearly warns against this and recommends using SQLite nativeUNIQUEconstraints or application-level exclusive locks. - [Critical] SEC-04: Similarly documented and added warnings for the TOCTOU race condition in
ForeignKeyHook, where a referenced key can be deleted between the constraint check and the write operation. Class docstring recommendsPRAGMA foreign_keys=ONfor strict referential integrity. - [High] SEC-05: Fixed a ReDoS (Regular Expression Denial of Service) vulnerability in
BaseHook'skey_patternregex parameter. Malicious regex patterns could cause excessive CPU load. Pattern validation is now enforced at construction time. - [High] SEC-06: Fixed information leakage in hook constraint violation error messages that exposed field names and values. Error messages are now generic, with detailed information logged server-side only.
Bug Fixes (v1.5.0 Pre-Release Audit)
- [Critical] BUG-05: Fixed
PydanticHooksilently converting all exceptions toValidationError. System-level errors such asConnectionErrorandMemoryErrorare now properly re-raised. - [High] BUG-06: Fixed unnecessary dictionary copying in hook processing when no values were actually changed. Introduced change detection to allocate new dicts only when values are actually modified (improves memory efficiency in batch operations).
Code Quality Fixes (PR Review Follow-up)
- [Low] BANDIT-B110: Replaced empty
try/except/passaroundatexit.unregisterinv2_engine.pywithcontextlib.suppress(Exception)to resolve the Bandit B110 warning. - [Low] POC Cleanup: Fixed all CodeQL and Bandit warnings raised in POC scripts (unused imports, unused variables, bare
except, and hard-coded ReDoS pattern literals). Removed duplicateimport sqlite3in test file.
Packaging and IDE Support Improvements
- [High] PEP 561 Compliance and Autocompletion Fix:
- Refactored
tool.setuptoolsinpyproject.tomlto use standardsrc-layoutauto-discovery. Fixed the issue where IntelliSense/autocompletion failed for the PyPI distribution. - Enabled
include-package-data = trueand addedMANIFEST.into ensure thepy.typedfile is correctly bundled in both wheel (.whl) and source distributions (sdist). - This enables full autocompletion support for
NanaSQLite,PydanticHook, and other exports in major IDEs like VS Code (Pylance) and PyCharm out of the box.
- Refactored
Improvements from Release Audit
- [Critical] BUG-01: Fixed a bug where
batch_update,batch_update_partial, andbatch_deletemethods bypassed V2 mode and performed direct database writes. Routed these operations through the V2 engine's staging buffer to ensure data integrity and FIFO order. - [Critical] BUG-02: Resolved "Ghost Re-inserts" in
clear()andload_all()methods, where database operations executed before the V2 engine's backgroundflush()completed. Introduced synchronous waiting viaflush(wait=True). - [High] QUAL-01: Refactored
AsyncNanaSQLite.add_hook()implementation to harden hook registration logic before and after base database initialization, improving stability in asynchronous environments. - [Non-Breaking] API Extension: Added a
waitparameter toflush()(sync) andaflush()(async) methods, allowing for synchronous waiting of background worker completion. - [High] Full Restoration of Python 3.9 Compatibility:
- Added
from __future__ import annotationsto all source files, allowing Python 3.10+|(Union) operators in type hints to function correctly on Python 3.9. - Introduced an
EllipsisTypecompatibility layer incompat.pyto ensure stablemypystatic analysis and runtime type validation on Python 3.9. - Updated
pyproject.tomlto targetmypyfor Python 3.9, guaranteeing continuous compatibility.
- Added
New Features: Ultimate Hooks (General-purpose Hook & Constraint Architecture)
- Powerful Hook Mechanism:
- Introduced the
NanaHookprotocol, allowing interception of 3 lifecycle events:before_write,after_read, andbefore_delete. - Custom hooks can be easily authored to implement data validation, custom encryption, logging, or integrations with external systems.
- Introduced the
- Built-in Standard Constraints:
CheckHook: Provides function-based validation similar to SQLite'sCHECKconstraint.UniqueHook: Ensures uniqueness of values for a specified key or nested field (TOCTOU warning applies, see SEC-03).ForeignKeyHook: Grants referential integrity against keys in otherNanaSQLitetables (TOCTOU warning applies, see SEC-04).
- Transparent External Library Integrations:
ValidkitHook: Maintains 100% backward compatibility with the legacyvalidatorparameter, providing high-performance validation viavalidkit-py.PydanticHook: Allows direct registration ofPydanticmodels as hooks, enabling automatic serialization/deserialization and strict type validation on read/write.
- Method Extensions:
- Added
NanaSQLite.add_hook()andAsyncNanaSQLite.add_hook()for dynamic hook registration.
- Added
Architectural Enhancements & Backward Compatibility
- The legacy
validatorparameter is internally converted to aValidkitHook, preserving 100% backward compatibility. - Internal logic has been unified and hardened to ensure hooks are equally applied across all access paths, including
batch_update,get,batch_get,setdefault, andpop.
Audit & Testing
- Updated pre-release audit report (
audit.md) — documented 12 findings for v1.5.0. - Added 5 POC scripts to
etc/poc/. - Added 14 POC verification tests to
tests/test_audit_poc.py.
[1.5.0dev2] - 2026-03-28 (released — consolidated into v1.5.0)
Packaging and IDE Support Improvements
- [High] PEP 561 Compliance and Autocompletion Fix:
- Refactored
tool.setuptoolsinpyproject.tomlto use standardsrc-layoutauto-discovery. Fixed the issue where IntelliSense/autocompletion failed for the PyPI distribution. - Enabled
include-package-data = trueand addedMANIFEST.into ensure thepy.typedfile is correctly bundled in both wheel (.whl) and source distributions (sdist). - This enables full autocompletion support for
NanaSQLite,PydanticHook, and other exports in major IDEs like VS Code (Pylance) and PyCharm out of the box.
- Refactored
[1.5.0dev1] - 2026-03-28
Improvements from Release Audit
- [Critical] BUG-01: Fixed a bug where
batch_update,batch_update_partial, andbatch_deletemethods bypassed V2 mode and performed direct database writes. Routed these operations through the V2 engine's staging buffer to ensure data integrity and FIFO order. - [Critical] BUG-02: Resolved "Ghost Re-inserts" in
clear()andload_all()methods, where database operations executed before the V2 engine's backgroundflush()completed. Introduced synchronous waiting viaflush(wait=True). - [High] QUAL-01: Refactored
AsyncNanaSQLite.add_hook()implementation to harden hook registration logic before and after base database initialization, improving stability in asynchronous environments. - [Non-Breaking] API Extension: Added a
waitparameter toflush()(sync) andaflush()(async) methods, allowing for synchronous waiting of background worker completion. - [High] Full Restoration of Python 3.9 Compatibility:
- Added
from __future__ import annotationsto all source files, allowing Python 3.10+|(Union) operators in type hints to function correctly on Python 3.9. - Introduced an
EllipsisTypecompatibility layer incompat.pyto ensure stablemypystatic analysis and runtime type validation on Python 3.9. - Updated
pyproject.tomlto targetmypyfor Python 3.9, guaranteeing continuous compatibility.
- Added
New Features: Ultimate Hooks (General-purpose Hook & Constraint Architecture)
- Powerful Hook Mechanism:
- Introduced the
NanaHookprotocol, allowing interception of 3 lifecycle events:before_write,after_read, andbefore_delete. - Custom hooks can be easily authored to implement data validation, custom encryption, logging, or integrations with external systems.
- Introduced the
- Built-in Standard Constraints:
CheckHook: Provides function-based validation similar to SQLite'sCHECKconstraint.UniqueHook: Ensures uniqueness of values for a specified key (or nested field).ForeignKeyHook: Grants referential integrity against keys in otherNanaSQLitetables.
- Transparent External Library Integrations:
ValidkitHook: Maintains 100% backward compatibility with the legacyvalidatorparameter, providing high-performance validation viavalidkit-py.PydanticHook: Allows direct registration ofPydanticmodels as hooks, enabling automatic serialization/deserialization and strict type validation on read/write.
- Method Extensions:
- Added
NanaSQLite.add_hook()andAsyncNanaSQLite.add_hook()for dynamic hook registration.
- Added
Architectural Enhancements & Backward Compatibility
- The legacy
validatorparameter is internally converted to aValidkitHook, preserving 100% backward compatibility. - Internal logic has been unified and hardened to ensure hooks are equally applied across all access paths, including
batch_update,get,batch_get,setdefault, andpop.
[1.4.1] - 2026-03-27
Security Fixes
- QUAL-07 [High]: Added V2 engine management methods to the synchronous
NanaSQLiteclass, achieving full feature parity between sync and async versions. - CORE [Critical]: Hardened V2 engine consistency in
clear(),load_all(), andrestore()methods to prevent data desynchronization and "ghost writes" during state transitions. - SEC-01/02 [Critical]: Introduced whitelist-based validation for
column_typewith ReDoS-safe patterns, enhancing protection against SQL injection and denial-of-service. - CONC-01/02 [High]: Fixed race conditions and deadlocks in the V2 engine and
ExpiringDictduring multi-threaded execution. - [Critical] PERF-02: Improved
table()method to share the parent'sV2Engineinstance. This resolves resource leaks (thread andatexithandler accumulation) that caused hangs during process exit. - [Critical] DEADLOCK-01: Resolved deadlocks in
V2EngineduringStrictTaskprocessing that caused processes to hang during parallel execution (e.g.,pytest-xdist). Implemented transaction isolation for task processing and reliable event release duringshutdown. - [Critical] MULTI-TENANT-01: Fixed a bug where
V2Enginewas tied to a single table name. Refactored the engine to support multi-tenancy (table-level isolation), ensuring data is not mixed when multiple table instances share the same engine. - [High] QUAL-08: Enhanced
V2Engine.shutdown()robustness with double-invocation prevention, reliableatexitunregistration, and safer final flush logic. - QUAL-05 [Medium]: Added guards against explicit
begin_transaction()calls in V2 mode to prevent conflicts with background flushing operations. - [Medium] SEC-02: Fixed the
column_typevalidation regular expression incore.pyfrom a vulnerable pattern ([\w ]*) to a safe pattern, completely resolving the ReDoS (Regular Expression Denial of Service) vulnerability warned by SonarQube.
Bug Fixes
- [High] BUG-01: Fixed
AttributeErrorinupsert()andaupsert()when passing a data dictionary as the first argument while specifyingconflict_columns. Improved internal logic to reference the correct keys intarget_data. (1.4.1rc1) - [High] QUAL-02: Fixed a potential race condition in
AsyncNanaSQLiteinitialization where multiple concurrent async tasks could trigger redundant background initializations. Introducedasyncio.Lockto ensure thread-safe startup. - Resolved syntax errors and initialization issues in
AsyncNanaSQLite.table()caused by docstring fragmentation and incomplete argument propagation. (1.4.1dev3) - Cleaned up duplicate method definitions in
AsyncNanaSQLitethat occurred during feature application. (1.4.1dev3)
Critical Fixes from Deep Audit
- [Critical] BUG-02: Resolved a "Stale Read" inconsistency in V2 mode where reading data via
get()or__getitem__immediately after a write could return outdated values. Optimized the read path to prioritize the background staging buffer. - [Critical] QUAL-04: Fixed a crash in
AsyncNanaSQLitewhen instantiated outside an event loop due to unsafeasyncio.Lock()initialization in__init__. Implemented lazy initialization for the lock within the event loop context. - [Critical] LOCK-01: Resolved a deadlock scenario in
ExpiringDictwhere the TTL expiration callback (on_expire) was executed while holding the DB lock, conflicting with concurrent write operations. Callbacks are now executed outside the locking scope. - [Critical] CONC-01: Fixed potential
RuntimeError, cache corruption, and TOCTOU races in multi-threaded environments (e.g.,AsyncNanaSQLite) by moving internal cache mutations into the scope of the database lock. - [Critical] CONC-02: Resolved a crash when using
table()in V2 mode where multiple background engines sharing the same SQLite connection would attempt to start overlapping transactions. Implementedshared_lockpropagation across parent/child V2 engines. - [Critical] ASYNC-01: Implemented missing V2 management methods (
aflush,aget_dlq,aretry_dlq,aclear_dlq,aget_v2_metrics) inAsyncNanaSQLite. - [High] QUAL-07: Added V2 management methods to the synchronous
NanaSQLiteclass, achieving full feature parity between sync and async engines. - [High] QUAL-05: Added guards to forbid explicit transaction operations (
begin_transaction, etc.) in V2 mode, preventing fatal conflicts with the engine's automated background flushing. - [High] QUAL-06: Fixed a bug where
v2_enable_metricssetting was not inherited by child instances inAsyncNanaSQLite.table(). - [Medium] SEC-01 (Hardened): Upgraded
create_table()column type validation from a blacklist approach to a strict whitelist-based regular expression for enhanced security.
Performance Improvements
- [Low] PERF-01: Introduced "negative caching" for LRU and TTL cache strategies to store the result of searches for keys that do not exist in the database, reducing I/O load during repeated access. (Also discovered and fixed a breaking bug before release where internal sentinels could leak due to this feature). (1.4.1rc1)
Code Quality Improvements
- [Low] QUAL-01: Improved the
ExpiringDictscheduler thread stop logic to ensure more robust cleanup during instance destruction or clearing. (1.4.1rc1) - [Low] QUAL-03: Deduplicated magic literals (e.g.,
"BEGIN IMMEDIATE") into module-level constants to improve maintainability. - [Low] CI-01: Resolved SonarQube Cloud "Quality Gate" false positives by excluding non-source files (docs, scripts) from coverage and suppressing non-essential maintainability warnings through configuration.
- [Low] QUAL-09: Removed unnecessary
.keys()calls inutils.py(list(dict.keys())→list(dict)) to address SonarCloud code smell warnings. - [Low] QUAL-10 (New Feature): Introduced
V2Configdataclass to group v2-related parameters (flush_mode,flush_interval,flush_count,chunk_size,enable_metrics) into a single object. All existing individual parameters remain available for full backward compatibility. This addresses SonarCloud's "brain-overload" warning for the__init__method having too many parameters.pythonfrom nanasqlite import NanaSQLite, V2Config cfg = V2Config(flush_mode="time", flush_interval=5.0, enable_metrics=True) db = NanaSQLite("mydata.db", v2_mode=True, v2_config=cfg) - [Low] CI-02: Added
docker rm -fbeforedocker runinbench-rpi.ymlto resolve container name conflicts ("Conflict. The container name is already in use") that occurred when a prior workflow run was cancelled.
New Features: Enhanced V2 Engine Usability and Observability (Opt-in)
- Dead Letter Queue (DLQ) Visibility:
- Added
get_dlq(),retry_dlq(), andclear_dlq()methods to both synchronous and asynchronous (a*) interfaces. - Allows direct inspection, manual retry, or clearing of background operation errors.
- Added
- Metrics Collection:
- Introduced a
v2_enable_metricsparameter to enable detailed engine statistics collection. get_v2_metrics()provides metrics such as total flush count, processing time, and DLQ error counts.
- Introduced a
- Configuration Inheritance:
- Ensured that V2-specific settings like
v2_enable_metricsare correctly propagated to child instances created via thetable()method.
- Ensured that V2-specific settings like
Documentation
- Enhanced API Documentation Generator: Overhauled
scripts/gen_api_docs.pyto produce modern, highly readable API references utilizing VitePress tables and custom containers. - Site-wide Documentation Modernization: Standardized all manual documentation by batch-converting callouts and warnings to the VitePress native format.
[1.4.0] - 2026-03-12
Security Fixes
- [Critical] SEC-01: Fixed SQL injection vulnerability in
create_table()column type definitions. APSW executes all semicolon-separated statements, allowing arbitrary SQL execution through crafted column type strings. Added validation that rejects column types containing;,--, or/*.
Bug Fixes
- [High] BUG-01: Fixed V2Engine
_process_strict_queue()callingon_successcallbacks before transaction COMMIT. If a later task failed and caused a ROLLBACK, earlier callers would receive false success notifications. Callbacks are now deferred until after COMMIT succeeds. - [Medium] BUG-02: Fixed
AsyncNanaSQLite.table()child instances missing_v2_mode,_cache_strategy,_encryption_keyand other attributes, causingAttributeError. All parent settings are now properly inherited. - [Medium] BUG-03: Fixed v2 mode
execute()returning empty results for SELECT/PRAGMA/EXPLAIN queries. Read queries now bypass the background queue and execute directly.
Code Quality Improvements
- [Low] BUG-04: Replaced duplicated alias extraction logic in
_shared_query_impl()with a call toNanaSQLite._extract_column_aliases(). - [Low] QUAL-01: Fixed
update()method type annotation fromdicttodict | None.
[1.4.0dev2] - 2026-03-12
Improvements: Async API Completion
- Implemented and exposed all key methods in
AsyncNanaSQLiteas asynchronous versions (abackup,arestore,apragma,aget_table_schema,alist_indexes,aalter_table_add_column,aupsert,aget_dlq,aretry_dlq, etc.) to achieve full feature parity with the synchronous version.
Changes: Unified and Enhanced upsert() Method
- Unified the
upsert()method signature to support both(table_name, data_dict, conflict_columns)and(key, value)patterns in a single method. - When
v2_modeis enabled, the(key, value)pattern is automatically routed to the background persistence queue.
Testing: Expanded Benchmark Coverage
- Increased benchmark tests from 158 to 177.
- Added coverage for previously unmeasured operations:
backup,restore,pragma,DDL (alter table/index),export/import, etc. - Significantly enhanced asynchronous benchmarks (
tests/test_async_benchmark.py).
Fixes
- Fixed
get_table_schemato accept an optionaltable_nameargument (defaulting to the current table) and handle cases where thetableproperty is missing. - Resolved all project-wide
rufflinting errors (31 items) andmypytype check issues.
[1.4.0dev1] - 2026-03-12
New Features: v2 Architecture (Optional)
- Non-blocking Background Persistence:
- Enable the v2 architecture by passing
v2_mode=TruetoNanaSQLite. - All write operations (KVS updates and explicit SQL execution) are temporarily buffered in memory or queued, and then flushed to SQLite asynchronously by a background thread.
- This eliminates disk I/O blocking on the main thread entirely, dramatically improving write latency.
- Read latency remains zero-cost as data is still fetched directly from the in-memory cache.
- Flush Modes: Customize flushing behavior using the
flush_modeparameter (immediate,count,time, ormanual). - Dead Letter Queue (DLQ): If a background SQL execution fails, the problematic task is isolated to a DLQ, allowing the rest of the data persistence pipeline to proceed without halting the system. Use
get_dlq()to inspect andretry_dlq()to re-enqueue failed tasks. - Chunk Flushing: Automatically splits large write batches (default: 1000 items) to prevent long-held database locks.
- Warning: The v2 architecture is designed exclusively for SINGLE-PROCESS systems. A warning is emitted if used in multi-process environments (e.g., Gunicorn with multiple workers) as parallel background threads will cause data corruption.
- Enable the v2 architecture by passing
Changes
- Added
v2_mode,flush_mode,flush_interval,flush_count, andv2_chunk_sizeparameters toNanaSQLiteandAsyncNanaSQLiteinitialization. - Added explicit
flush()(sync) andaflush()(async) methods. - Added
get_dlq()andretry_dlq()methods toV2Enginefor DLQ management.
Fixes
- Fixed a race condition when accessing the Dead Letter Queue (DLQ) concurrently in the v2 engine.
- Fixed a bug where strict queue tasks were not processed if the KVS staging buffer was empty.
[1.3.4] - 2026-03-10
Security Fixes
- SEC-01 [High]: Switched
alter_table_add_column()column_typevalidation from blacklist to whitelist regex. Reliably blocks injection payloads likeTEXT; DROP TABLE. - SEC-02 [High]: Fixed
sanitize_sql_for_function_scan()to preserve double-quoted SQL identifier content._validate_expression()now correctly detects quoted function name bypasses like"LOAD_EXTENSION"().
Bug Fixes
- BUG-01 [Critical]: Added
_check_connection()check toitems(). Calling on a closed instance now raisesNanaSQLiteClosedErrorinstead of leaking a low-level APSW exception. - BUG-02 [High]: AEAD deserialization now logs a warning instead of silently falling back to plaintext JSON when receiving non-bytes values.
- BUG-03 [High]: Added payload length validation (≥28 bytes = 12-byte nonce + 16-byte auth tag) before AEAD decrypt. Short data now raises a clear
NanaSQLiteDatabaseError.InvalidTagand other low-level crypto exceptions are also wrapped intoNanaSQLiteDatabaseError. - BUG-04 [High]: Removed redundant double
_ensure_initialized()call inAsyncNanaSQLite.acontains(). - BUG-05 [Medium]: Added
offsettype and non-negative validation in async_shared_query_impl(). - BUG-06 [Medium]: Fixed
parameters: tuple = None→tuple | None = Nonetype annotations inasync_core.py(mypy strict compliance). - BUG-07 [Medium]:
ExpiringDictscheduler now processes all expired keys per iteration instead of just one. - BUG-09 [Medium]:
batch_get()now correctly includes keys with explicitNonevalues in results. - BUG-10 [Low]: Reuse compiled
IDENTIFIER_PATTERNin_sanitize_identifier(). - BUG-12 [Low]: Fixed
NanaSQLiteDatabaseError.__init__original_errortype annotation toException | None.
Performance Improvements
- PERF-03 [Medium]: Extracted
_extract_column_aliases()helper, deduplicating column-alias extraction from 3 call sites.
Code Quality
- QUAL-01 [Medium]: Fixed
_get_all_keys_from_db()return type tolist[str]. - QUAL-03 [Medium]: Harmonized column-name quote stripping between
query()andquery_with_pagination().
Audit & Testing
- Added pre-release audit report (
audit.md) — 35 findings documented. - Added 6 POC scripts in
etc/poc/. - Added 20 POC verification tests in
tests/test_audit_poc.py. - Updated
audit_prompt.mdto 6-phase workflow (audit → POC → patch → pytest → CI verification → release preparation).
[1.3.4rc4] - 2026-03-08
CI Fixes
- Least-privilege cleanup for the provenance job (PR #127):
- Downgraded
contents: writetocontents: readin theprovenancejob; write access was only needed forupload-assets, which was already removed. - Removed the dead
upload-assets: trueoption — this workflow has no tag-based trigger, so the SLSA generator would always skip it. - Provenance is still attached to GitHub Releases by the
releasejob as before. - Added inline comments explaining the two expected CI annotations (
go.sum not foundwarning and PyPI attestation notice) to prevent confusion. - Synced
CHANGELOG.mdfrom the latestmainbranch.
- Downgraded
[1.3.4rc3] - 2026-03-08
CI Fixes
- Restored and hardened the SLSA3 provenance release flow (PR #123):
- Added
actions: readandcontents: readpermissions to the provenance verification job in GitHub Actions. - Constructed the expected provenance filename from the
provenance-nameoutput and now fail fast if the file is missing. - Updated GitHub Release asset upload to reference the exact generated provenance file instead of a wildcard, preventing release-time artifact mismatches.
- Added
[1.3.4rc2] - 2026-03-08
Security Fixes
- Implemented SQL injection protection for table names (PR #121, #122):
- Table names were interpolated directly into SQL queries, making crafted names exploitable for injection.
- Sanitized (double-quoted) table name is now cached in
self._safe_tableand used in all SQL execution paths. self._tableretains the raw name for__repr__and backwards compatibility.- Updated SECURITY.md with disclosure history and remediation details.
- Added PoC scripts (
etc/poc/poc_sqli.py,etc/poc/poc_none.py) to document the risk.
Bug Fixes & Code Quality
- Applied
_NOT_FOUNDsentinel toget_fresh()and__contains__(PR #121):get_fresh()previously returnedNoneon a DB miss, making it impossible to distinguish from a storedNonevalue.- Switched to the
_NOT_FOUND = object()sentinel so DB misses and storedNoneare reliably distinguished. - Restored a lightweight
__contains__implementation to reduce unnecessary DB reads.
CI Fixes
- Fixed validkit-py CI test guards (PR #119):
- Updated CI to install the
validationextra so validkit-related tests are executed correctly.
- Updated CI to install the
Documentation
- Added validkit-py validation guide (PR #117):
- Added validkit-py usage and validation guides to both the English and Japanese documentation sites.
- Reordered and classified guide lessons (PR #116):
- Reorganised and categorised guide lessons in the JA/EN site documentation.
- Fixed docs inconsistencies, broken links, and factual errors (PR #115):
- Resolved inconsistencies between English and Japanese documentation, fixed broken links, corrected factual errors, and added missing documentation.
[1.3.4rc1] - 2026-03-07
New Features
- Added
batch_update_partial()method (sync and async):- New method that writes a batch in "best-effort" mode when a
validatoris set. - Each entry is validated individually; only entries that pass are written to the database.
- Returns a
dictof{key: error_message}for failed entries — no exception is raised. - When
coerce=True, coerced values are stored for successful entries. - The existing
batch_update()retains its atomic behaviour (all-or-nothing). - Async counterpart added as
AsyncNanaSQLite.abatch_update_partial().
- New method that writes a batch in "best-effort" mode when a
Bug Fixes & Code Quality
- Fixed mypy error in
core.py:_serialize()returnedjson_strwhich mypy inferred asstr | Nonein theHAS_ORJSON=Falsepath; suppressed withtype: ignoresincejson_stris guaranteedstrat that point.
- Fixed ruff violations in examples:
examples/test_examples.py: import sort (I001),assert False→raise AssertionError()(B011), class name to CapWords (N801).examples/validkit_batch_demo.py: import sort (I001).
Added Examples
- Added
examples/validkit_batch_demo.py:- Demonstrates atomic
batch_update()and best-effortbatch_update_partial(). - Includes
coerce=Trueusage with field-level.coerce().
- Demonstrates atomic
- Extended
examples/test_examples.pywith validkit batch operation validation:- Atomic rollback verification, partial write verification, coerce mode verification.
[1.3.4b3] - 2026-03-05
Bug Fixes & Stability Improvements
Fixed test instability on Python 3.9 (
tests/test_tdd_cycle_6.py) (PR #113):test_ellipsis_type_is_availablechecks fortypes.EllipsisType(added in Python 3.10), but was unconditionally asserting its presence and therefore always failed on Python 3.9.- Added
@pytest.mark.skipif(sys.version_info < (3, 10), ...)so the test is skipped on Python 3.9 and still runs on Python 3.10+. - Because both
core.pyandasync_core.pyusefrom __future__ import annotations, thetypes.EllipsisTypein their type annotations is stored as a string and is never evaluated at runtime, so the library itself already works correctly on Python 3.9. This was a test-only issue. - No impact on library behaviour or public API.
Fixed
table()cache settings inheritance (PR #112):- Child instances created via
table()did not inheritcache_ttl/cache_persistence_ttlfrom their parent, causingValueErrorwhen the parent used a TTL cache strategy. - Introduced
_cache_strategy_raw,_cache_size_raw,_cache_ttl_raw, and_cache_persistence_ttl_rawto store the original arguments;table()now propagates all cache settings correctly.
- Child instances created via
AsyncNanaSQLitenow raisesImportErroreagerly when validkit-py is missing (PR #112):- Previously the error was deferred until a write occurred.
AsyncNanaSQLite.__init__now raisesImportErrorimmediately whenvalidatoris supplied without validkit-py installed, aligning behaviour with the synchronousNanaSQLite. - Added
HAS_VALIDKITflag toasync_core.py.
- Previously the error was deferred until a write occurred.
Exception narrowing in
core.py:- Replaced broad
except Exception:clauses guarding optional imports (orjson / validkit-py) with the more specificexcept ImportError:.
- Replaced broad
Type annotation fixes:
- Added
"ttl"to theLiteraltype of thecache_strategyargument intable(). - Changed the
_UNSETsentinel type annotation totypes.EllipsisTypefor improved type safety.
- Added
mypy configuration update (
pyproject.toml):- Bumped
python_versionfrom3.9to3.10so thattypes.EllipsisTypeis recognised during static type checking.
- Bumped
API Documentation Fixes (PR #112)
- Updated
NanaSQLite.table()andAsyncNanaSQLite.table()API docs (English and Japanese) to showvalidator=...andcoerce=...(sentinel default indicating parent-inheritance).
Tests & Quality Improvements (PR #112)
- Added comprehensive test suites:
tests/test_table_inheritance_comprehensive.py: 75 test cases covering alltable()inheritance scenarios.tests/test_validkit_integration.py: Integration tests for validkit-py (sync and async).tests/test_tdd_review_fixes.py: Regression tests for review-comment fixes.tests/test_tdd_cycle_2.pythroughtests/test_tdd_cycle_10.py: Per-cycle regression tests.
- Improved validkit availability check:
- Replaced
importlib.util.find_specwith atry/except importcheck so broken installations are also correctly detected.
- Replaced
[1.3.4b2] - 2026-03-04
New Features
validatorparameter (optional dependency: validkit-py):- Added
validatorparameter toNanaSQLite.__init__andAsyncNanaSQLite.__init__. - Accepts a validkit-py schema (plain dict or
Schemaobject). When supplied, values are validated before every write. - Raises
NanaSQLiteValidationErroron schema violation. - Raises
ImportErrorwith an install hint whenvalidatoris supplied butvalidkit-pyis not installed. - Install via
pip install nanasqlite[validation]. - Exposes
HAS_VALIDKITflag from thenanasqlitepackage (andcoremodule).
- Added
Per-table
validatorsupport intable():- Added
validatorparameter toNanaSQLite.table()andAsyncNanaSQLite.table(). - Different schemas can now be applied per sub-table.
- When
validatoris omitted, the parent instance's schema is inherited automatically.
- Added
coerceparameter (auto-conversion option):- Added
coerce: bool = Falseparameter toNanaSQLite.__init__,NanaSQLite.table(),AsyncNanaSQLite.__init__, andAsyncNanaSQLite.table(). - When
True, the coerced value returned by validkit-py (e.g."42"→42) is stored instead of the original value. - Important: Auto-conversion requires both
coerce=TrueonNanaSQLiteAND.coerce()on each field validator in the schema (e.g.,v.int().coerce()). Without.coerce()on the field, values whose types don't match the schema will still raiseNanaSQLiteValidationErroreven withcoerce=True. - Works in conjunction with
validator; has no effect when no validator is set. - When omitted in
table(), the parent'scoercesetting is inherited automatically.
- Added
batch_update()validation support:- When a
validatoris set,batch_update()now validates all values before touching the database. - If any value fails validation, nothing is written (atomic failure guarantee).
- When
coerce=True, coerced values are bulk-written instead of the originals.
- When a
Bug Fixes
table()no longer drops the parentvalidatoron child instances:- In b1, child instances created via
table()did not inherit_validator, so writes to sub-tables bypassed validation entirely. - The same issue was present in
AsyncNanaSQLite.table()where_validatorwas never assigned toasync_sub_db; this is now fixed.
- In b1, child instances created via
[1.3.4b1] - 2026-03-04
New Features
lock_timeoutparameter (P2-1):- Added
lock_timeout: float | None = Noneparameter toNanaSQLite.__init__. - When set, raises
NanaSQLiteLockErrorif the lock cannot be acquired within the specified seconds. - Default
Nonepreserves the existing unlimited-wait behaviour. Fully backward-compatible. - Introduced
_acquire_lock()context manager internally so user-facing exclusive operations respect the timeout (some internal operations such as TTL expiry deletion continue to use blocking acquisition).
- Added
backup()/restore()methods (P2-3):NanaSQLite.backup(dest_path): Backs up the current database todest_pathusing APSW's SQLite online backup API.NanaSQLite.restore(src_path): Restores the database from a backup file, re-establishes the connection, and clears the in-memory cache. Explicitly removes WAL/SHM/journal sidecar files (-wal/-shm/-journal) before reopening to prevent stale WAL replay causing an inconsistent state.- Both are new public methods only; no backward-compatibility impact.
Thread Safety Improvements
- Lock-protected child instance creation in
table():- Wrapped child instance creation and
WeakSetregistration intable()with_acquire_lock()to prevent race conditions withrestore()'s connection replacement, eliminating the risk of child instances referencing a closed connection.
- Wrapped child instance creation and
Bug Fixes
- Added
_check_connection()to__delitem__:del db[key]on a closed connection now raisesNanaSQLiteClosedErrorconsistently, matching the behaviour of__setitem__,pop(), andclear().
[1.3.4b0] - 2026-03-04
Code Quality Improvements
- Async pool cleanup log level fix:
- Changed the log level from
ERRORtoWARNINGforAttributeErroroccurrences during read-only pool drain inAsyncNanaSQLite.close(). - Updated the comment wording from "Programming error" to "Unexpected AttributeError - log and continue cleanup for resilience" to better reflect intent.
- Log output only; no behaviour or backward-compatibility impact.
- Changed the log level from
Documentation & Planning
- Added v1.3.x plan review document (
etc/in_progress/v1.3.x_plan_review.md):- Cross-referenced all
etc/planning docs against the v1.3.x changelog to surface remaining work and set priorities. - Documented priorities for roadmap Phase 2 items still outstanding (lock timeout, validation foundation, backup/restore).
- Included a draft release schedule from v1.3.4b0 through v1.4.0.
- Cross-referenced all
- Updated
etc/README.md: Added the new review document to thein_progress/table. - Reorganised
etc/directory (PR #109):- Replaced the flat
future_plans/folder with three status-based subdirectories:implemented/,in_progress/, andplanned/. - Verified that all v1.3.0 cache features (
ExpiringDict,UnboundedCache,TTLCache, etc.) are fully implemented.
- Replaced the flat
Dependency Updates (docs/site Maintenance)
- docs/site dependency updates (Renovate):
[1.3.4dev0] - 2026-03-02
CI / Development Environment
- SLSA provenance cache restore warning — investigation and revert:
- Added an empty
go.sumat the repo root to suppress theRestore cache failedwarning emitted by theprovenance / generatorjob (PR #103). - Determined that the fix was ineffective: the
provenance / generatorjob runs on an isolated runner that does not check out this repository, so the warning cannot be silenced by a local file. The emptygo.sumwas subsequently removed (PR #104).
- Added an empty
Other
- Bumped version to
1.3.4dev0(development snapshot following the1.3.3release).
[1.3.3] - 2026-03-02
Security
- docs/site dependency vulnerability fixes:
CI / Development Environment
- GitHub Actions updates:
Dependency Updates (Maintenance)
- Release automation action update:
- Updated
softprops/action-gh-releaseto v2. (#96)
- Updated
Notes
- This release is primarily a maintenance update (security/CI/dependency bumps) and does not include breaking changes to the public API.
[1.3.2] - 2026-01-17
Performance Optimization
- orjson Integration Refinement:
- Removed unnecessary variable allocation in
_serialize()method to improve code readability and maintainability. - Verified and validated that orjson JSON encoding/decoding is effectively utilized across all encryption paths (Fernet, AES-GCM, ChaCha20).
- Expected 3-5x performance improvement compared to standard
jsonmodule. - Confirmed that async processing (
AsyncNanaSQLite) automatically benefits from orjson via ThreadPoolExecutor.
- Removed unnecessary variable allocation in
Code Quality Improvements
- Core Code Optimization:
- Enhanced code readability and clarified variable scope.
Testing & Validation
- orjson Tests Verification:
- Confirmed all tests in
tests/test_json_backends.pyrun correctly. - Verified compatibility in both orjson-available and fallback environments.
- Confirmed automatic JSON backend switching (HAS_ORJSON flag) functions correctly.
- Confirmed all tests in
[1.3.1] - 2025-12-28
New Features: Optional Data Encryption
- Multi-mode Encryption: Transparent encryption using
cryptography.- AES-GCM (Default): Secure and fast, optimized for hardware acceleration (AES-NI).
- ChaCha20-Poly1305: High software-only performance, ideal for devices without AES-NI.
- Fernet: High-level API for compatibility and ease of use.
- Added
encryption_keyandencryption_modeparameters toNanaSQLiteandAsyncNanaSQLite.
- Extra Installation:
pip install nanasqlite[encryption]to install required dependencies.
New Features: Flexible Cache Strategy & TTL Support (v1.3.1-alpha.0)
- TTL (Time-To-Live) Cache: Set expiration for cached data using
cache_strategy=CacheType.TTL, cache_ttl=seconds. - Persistence TTL: Automatically delete expired data from the SQLite database with
cache_persistence_ttl=True. - FIFO-limited Unbounded Cache: Specify
cache_sizeinUNBOUNDEDmode for FIFO (First-In-First-Out) eviction. - Cache Clearing API: Added
db.clear_cache()and asyncaclear_cache().
Improvements & Fixes
- Optimized
ExpiringDict: Internal utility for high-precision, low-overhead expiration management. - Maintained Performance: Preserved the fast-path for the default
UNBOUNDEDmode while ensuring limits are strictly enforced when configured. - Enhanced Type Safety: Fully compliant with
mypyandruffstrict checks. - Unified Benchmarks: Consolidated encryption and cache strategy benchmarks into
tests/test_benchmark.py(Sync) andtests/test_async_benchmark.py(Async). - Test Coverage: Added
tests/test_async_cache.pyto verify async cache behaviors (LRU eviction, TTL expiration).
[1.3.0dev0] - 2025-12-27
New Features: Flexible Cache Strategy
- Added
CacheTypeEnum: Choose betweenUNBOUNDED(infinite, legacy behavior) andLRU(eviction-based). - LRU Cache Implementation: Limit memory usage with
cache_strategy=CacheType.LRU, cache_size=N. - Per-Table Configuration: Configure specific tables via
db.table("logs", cache_strategy=CacheType.LRU, cache_size=100). - Performance Option: Install
lru-dictC-extension viapip install nanasqlite[speed]for up to 2x speedup. - Automated Fallback: Automatically falls back to standard library
OrderedDictiflru-dictis not installed.
New Tests
tests/test_cache.py: Comprehensive test suite for cache strategies (eviction, persistence, per-table configuration).
[1.2.2b1] - 2025-12-27
Documentation & Brand Overhaul
- Ultra-Modern Documentation Site:
- Built a new high-end official site using VitePress + Tailwind CSS in
docs/site, significantly improving design and UX. - Official SVG Identity: Created an original 'Dict-Stack' symbol. Features 100% transparency, automatic dark mode support (via inverted filters), and infinite vector resolution.
- Truly Isolated Bilingual API Docs: Implemented an intelligent extraction engine to parse docstrings and generate purely localized references for both Japanese and English.
- Built a new high-end official site using VitePress + Tailwind CSS in
- Automation & Deployment:
- Introduced automated deployment via GitHub Actions (
deploy-docs.yml). - Implemented smart history preservation that automatically merges previous benchmark data from
gh-pagesinto the new documentation build.
- Introduced automated deployment via GitHub Actions (
Security & CI Improvements
- SQL Validation Refinement:
- Added the
||(concatenation) operator to the fast-validation safe set, resolving false positives in complex SQL alias queries.
- Added the
- CI/CD Stability:
- Strict re-sorting of imports in
core.pyandgen_api_docs.pyto comply with the latestrufflinting rules. - Enhanced dependency management for documentation builds.
- Strict re-sorting of imports in
[1.2.2a1] - 2025-12-26
Development Tools (Benchmarks & CI/CD)
- Fixed Benchmark Comparison Logic:
- Standardized comparison to use ops/sec; higher values now correctly show as positive (🚀/✅) improvements.
- Added absolute ops/sec difference (e.g.,
+2.1M ops) to the performance summary table. - Ops/sec Accuracy: Switched to using raw
opsdata from the benchmark tool instead of calculating from mean time (approximation). This also fixed the bug where OS details showed(0.0). - Corrected time formatting for sub-microsecond values to explicitly use
ns(nanoseconds). - Introduced status emojis (🚀, ✅, ➖, ⚠️, 🔴) for quick visual performance assessment.
- Workflow Optimizations:
benchmark.yml: Changed benchmarks to be informational-only to prevent CI failures caused by GitHub Actions runner performance variance (~10-60%).ci.yml: Optimized triggers by restricting automaticpushruns to themainbranch. Addedworkflow_dispatchfor manual runs on other branches.- Simplified
should-runcheck logic.
[1.2.1b2] - 2025-12-25
Development Tools
- CI/CD Workflow Consolidation:
- Consolidated
lint.yml,test.yml,publish.yml, andquality-gate.ymlinto a singleci.yml. - Added direct links to PyPI and GitHub Release, and detailed job statuses (Cancelled/Skipped support) in the final summary.
- Consolidated
- Test Environment Optimization:
- Refined the CI test matrix. Ubuntu runs all versions, while Windows/macOS focus on popular versions (3.11 and 3.13) to reduce execution time.
- Added
pytest-xdistto dev dependencies for parallel testing support.
- Type Checking Improvements:
- Resolved 156 mypy errors by refining the configuration (introduced
--no-strict-optionaland fine-tuned error code controls).
- Resolved 156 mypy errors by refining the configuration (introduced
Development Tools
- Lint & CI Environment:
- Added
tox.iniwith environments fortox -e lint(ruff),tox -e type(mypy),tox -e format, andtox -e fix. - Added ruff configuration to
pyproject.toml(E/W/F/I/B/UP/N/ASYNC rules, Python 3.9+ support, line-length: 120). - Added mypy configuration to
pyproject.toml(using--no-strict-optionalflag for practical type checking). - Added
.github/workflows/lint.yml: PyPA/twine-style CI workflow with tox integration, FORCE_COLOR support, and summary output. - Added
.github/workflows/quality-gate.yml: All-green gate with main branch detection and publish readiness check. - Added dev dependencies:
tox>=4.0.0,ruff>=0.8.0,mypy>=1.13.0.
- Added
- Code Quality Improvements:
- Fixed 1373 lint errors via ruff auto-fix (import ordering, unused imports removal, pyupgrade, whitespace, etc.).
- Added B904 (raise without from) and B017 (assert raises Exception) to ignore list.
- Adjusted mypy configuration for practical use (156 errors → 0 errors).
[1.2.0b1] - 2025-12-24
Security & Robustness
- Enhanced
ORDER BYParsing:- Implemented a dedicated parser
_parse_order_by_clauseinNanaSQLiteto safely handle and validate complexORDER BYclauses. - Improved protection against SQL injection while supporting legitimate complex sorting patterns.
- Implemented a dedicated parser
- Strict Validation Fixes:
- Standardized error messages for dangerous patterns (
;,--,/*) to consistently follow theInvalid [label]: [message]format. - Ensured consistent behavior between legacy and new security tests by applying a unified message format for all validation failures.
- Standardized error messages for dangerous patterns (
Refactoring
- Code Organization:
- Extracted
_sanitize_sql_for_function_scanlogic to a newnanasqlite.sql_utilsmodule for better maintainability. - Eliminated code duplication in
AsyncNanaSQLiteby consolidatingqueryandquery_with_paginationmethods into a shared_shared_query_implhelper method (~150 lines reduced).
- Extracted
- Type Safety:
- Added
Literaltype hints forcontextparameter to improve IDE support and type checking (PR #36).
- Added
Fixes & Improvements
- Async Logging:
- Increased log level from DEBUG to WARNING for errors occurring during read-pool cleanup to ensure resource issues are visible.
- Added connection context to cleanup error messages.
- Improved Async Pool Cleanup Robustness:
- Enhanced
AsyncNanaSQLite.close()method to ensure all pool connections are cleaned up even if some connections encounter errors. - Changed error handling to continue cleanup instead of breaking on
AttributeError, preventing resource leaks.
- Enhanced
- Tests:
- Fixed
__eq__method to correctly propagateNanaSQLiteClosedErrorwhen instances are closed (PR #44). - Improved exception handling specificity in security tests (PR #43).
- Clarified comments in security tests regarding validation timing (PR #35).
- Removed duplicate
pytestimports and cleaned up temporary test files (temp_test_parser.py).
- Fixed
[1.2.0a2] - 2025-12-23
- Enhanced Async Security Features:
- Fixed
AsyncNanaSQLite.queryandquery_with_paginationto correctly passallowed_sql_functions,forbidden_sql_functions, andoverride_allowedto_validate_expression. - Added comprehensive asynchronous security tests in
tests/test_security_async_v120.py.
- Fixed
- Improved Async Connection Management:
- Added
_closedflag toAsyncNanaSQLiteto track the connection state. - Improved child instance invalidation: sub-instances created via
table()are now immediately marked as closed when the parent is closed. - Fixed
close()behavior to ensure that even uninitialized instances correctly transition to a closed state, raisingNanaSQLiteClosedErroron subsequent operations.
- Added
[1.2.0a1] - 2025-12-23
- Async Read-Only Connection Pool:
- Added
read_pool_sizelogic toAsyncNanaSQLite. - Enables parallel execution for
query,query_with_pagination,fetch_all,fetch_one. - Enforces
read-onlymode for pool connections for safety.
- Added
- Bug Fixes:
- Fixed
apsw.ExecutionCompleteErroroccurring inqueryandquery_with_paginationwhen results are empty (0 rows). - Aligned column metadata extraction with sync implementation using
PRAGMA table_infoand manual parsing instead of relying oncursor.description.
- Fixed
[1.2.0dev1] - 2025-12-23
Fixed
- Async API Consistency:
- Added
a-prefixed aliases for all methods inAsyncNanaSQLite(e.g.,abatch_update,ato_dict). - Resolved "method not defined" errors in
test_async_benchmark.py.
- Added
- Backward Compatibility Fixes:
- Re-aligned SQL injection error messages to match legacy test expectations (e.g., "Invalid order_by clause").
- Updated
test_enhancements.pyto handleNanaSQLiteClosedErroralongside class name checks.
- Windows Stability:
- Refactored
test_security_v120.pyto usetmp_pathfixture, resolvingBusyErrorandIOErroron Windows.
- Refactored
query/query_with_paginationBug Fix:- Fixed issue where
limit=0andoffset=0were ignored. Changedif limit:toif limit is not None:. - ⚠️ Backward Compatibility: Previously, passing
limit=0returned all rows. Now it correctly returns 0 rows. If you usedlimit=0to mean "no limit", change tolimit=None.
- Fixed issue where
- Edge Case Tests Added:
- Created
tests/test_edge_cases_v120.pywith tests for emptybatch_*operations and pagination boundary conditions.
- Created
[1.2.0dev0] - 2025-12-22
Added
- Security Enhancements (Phase 1):
- Introduced
strict_sql_validationflag (Exception or Warning for unauthorized functions). - Introduced
max_clause_lengthto limit dynamic SQL length (ReDoS protection). - Enhanced detection for dangerous SQL patterns (
;,--,/*) and keywords (DROP,DELETE, etc.).
- Introduced
- Strict Connection Management:
- Introduced
NanaSQLiteClosedError. - Implemented child instance tracking/invalidation when the parent instance is closed.
- Introduced
- Maintenance:
- Created
DEVELOPMENT_GUIDE.md(Bilingual). - Codified environment sync rule:
pip install -e . -U.
- Created
[1.1.0] - 2025-12-19
Added
Custom Exception Classes:
NanaSQLiteError(base class)NanaSQLiteValidationError(validation errors)NanaSQLiteDatabaseError(database operation errors)NanaSQLiteTransactionError(transaction-related errors)NanaSQLiteConnectionError(connection errors)NanaSQLiteLockError(lock errors, for future use)NanaSQLiteCacheError(cache errors, for future use)
Batch Retrieval (
batch_get):- Efficiently load multiple keys with
batch_get(keys: List[str]) - Async support via
AsyncNanaSQLite.abatch_get(keys) - Optimizes cache by fetching multiple items in a single query
- Efficiently load multiple keys with
Enhanced Transaction Management:
- Transaction state tracking (
_in_transaction,_transaction_depth) - Detection and error reporting for nested transactions
- Added
in_transaction()method - Prevention of connection closure during transactions
- Detection of commit/rollback outside transactions
- Transaction state tracking (
Async Transaction Support:
AsyncNanaSQLite.begin_transaction()AsyncNanaSQLite.commit()AsyncNanaSQLite.rollback()AsyncNanaSQLite.in_transaction()AsyncNanaSQLite.transaction()(context manager)_AsyncTransactionContextclass implementation
Resource Leak Prevention:
- Parent instance tracks child instances with weak references
- Notification to child instances when parent is closed
- Prevention of orphaned child instance usage
- Added
_check_connection()method - Added
_mark_parent_closed()method
Improvements
Enhanced Error Handling:
- Added error handling to
execute()method - Wraps APSW exceptions with
NanaSQLiteDatabaseError - Preserves original error information (
original_errorattribute) - Added connection state checks to each method
- Uses
NanaSQLiteValidationErrorin_sanitize_identifier()
- Added error handling to
Added connection check to
__setitem__method
Documentation
New Documentation:
docs/en/error_handling.md- Error handling guidedocs/en/transaction_guide.md- Transaction guidetests/test_enhancements.py- Tests for enhanced features (21 tests)
README Updates:
- Added transaction support section
- Added custom exception sample code
- Added async transaction samples
Tests
- New Tests (21 tests):
- Custom exception class tests (5 tests)
- Transaction feature enhancement tests (6 tests)
- Resource management tests (3 tests)
- Error handling tests (2 tests)
- Transaction and exception combination tests (2 tests)
- Async transaction tests (3 tests)
Fixes
- Fixed security tests to expect
NanaSQLiteValidationError
[1.1.0a3] - 2025-12-17
Documentation Improvements
- Added usage notes for
table()method:- Added important usage notes section to README.md (English & Japanese)
- Warning about creating multiple instances for the same table
- Recommendation to use context managers
- Best practices clarification
- Improved docstrings:
- Added detailed notes to
NanaSQLite.table()docstring - Added detailed notes to
AsyncNanaSQLite.table()docstring - Added specific examples of deprecated and recommended patterns
- Added detailed notes to
- Future improvement plans:
- Documented improvement proposals in
etc/future_plans/directory - Duplicate instance detection warning feature (Proposal B)
- Connection state check feature (Proposal B)
- Shared cache mechanism (Proposal C - on hold)
- Documented improvement proposals in
Analysis & Investigation
- Comprehensive investigation of table() functionality:
- Stress tests: All 7 tests passed
- Edge case tests: 10 tests conducted
- Concurrency tests: All 5 tests passed
- Issues found: 2 (minor design limitations)
- Cache inconsistency with multiple instances for same table (addressed with documentation)
- Sub-instance access after close (addressed with documentation)
- Conclusion: Ready for production use, no performance issues
[1.1.0dev2] - 2025-12-16
Current Development Status
- Development version in progress
- Testing in progress (all 15 tests in
test_concurrent_table_writes.pypassing)
[1.1.0dev1] - 2025-12-15
Added
- Multi-table Support (
table()method): Safely operate on multiple tables within the same database- Get an instance for another table with
db.table(table_name) - Shared connection and lock: Multiple table instances share the same SQLite connection and thread lock
- Thread-safe: Concurrent writes to different tables from multiple threads work safely
- Memory efficient: Reuses connections to save resources
- Sync version:
NanaSQLite.table(table_name)→NanaSQLiteinstance - Async version:
await AsyncNanaSQLite.table(table_name)→AsyncNanaSQLiteinstance - Cache isolation: Each table instance maintains independent in-memory cache
- Get an instance for another table with
Internal Implementation Improvements
- Enhanced thread safety: Added
threading.RLockto all database operations- Read (
_read_from_db), write (_write_to_db), delete (_delete_from_db) - Query execution (
execute,execute_many) - Transaction operations
- Read (
- Improved connection management:
_shared_connectionparameter for connection sharing_shared_lockparameter for lock sharing_is_connection_ownerflag for connection ownership managementclose()method only executed by connection owner
Tests
- 15 comprehensive test cases (all passing):
- Sync multi-table concurrent write tests (2 tables, multiple tables)
- Async multi-table concurrent write tests (2 tables, multiple tables)
- Stress test (1000 concurrent writes)
- Cache isolation tests
- Table switching tests
- Edge case tests
Compatibility
- Full backward compatibility: No impact on existing code
- All new parameters are optional (internal use)
[1.0.3rc7] - 2025-12-10
Added
- Async Support (AsyncNanaSQLite): Complete async interface for async applications
AsyncNanaSQLiteclass: Provides async versions of all operations- Dedicated ThreadPoolExecutor: Configurable max_workers (default 5) for optimization
- High-performance concurrent processing with
ThreadPoolExecutor - Safe to use with async frameworks like FastAPI, aiohttp
- Async dict-like interface:
await db.aget(),await db.aset(),await db.adelete() - Async batch operations:
await db.batch_update(),await db.batch_delete() - Async SQL execution:
await db.execute(),await db.query() - Async context manager:
async with AsyncNanaSQLite(...) as db: - Concurrent operations support: Multiple async operations can run concurrently
- Automatic resource management: Thread pool auto-cleanup
- Comprehensive test suite: 100+ async test cases
- Basic operations, concurrency, error handling, performance tests
- All tests passing
- Full backward compatibility: Existing
NanaSQLiteclass unchanged
Performance Improvements
- Prevents blocking in async apps, improving event loop responsiveness
- Dedicated thread pool enables highly efficient concurrent processing (configurable workers)
- Optimal performance with APSW + thread pool combination
- Tunable max_workers for high-load environments (5-50)
[1.0.3rc6] - 2025-12-10
Added
get_fresh(key, default=None)method: Read directly from DB, update cache, and return value- Useful for cache synchronization after direct DB changes via
execute() - Uses
_read_from_dbdirectly to minimize overhead
- Useful for cache synchronization after direct DB changes via
[1.0.3rc5] - 2025-12-10
Performance Improvements
batch_update()optimization: 10-30% faster withexecutemanybatch_delete()optimization: Faster bulk deletion withexecutemany__contains__()optimization: Lightweight EXISTS query (faster for large values)
IDE/Type Support Enhancements
- Added
from __future__ import annotations - Specific type annotations:
Dict[str, Any],Set[str] - Clearer parameter types:
Optional[Tuple]
Documentation
- Added cache consistency warning to
execute()method - Improved docstrings (Returns, Warning sections)
Bug Fixes
- Resolved Git merge conflicts (order_by regex validation)
- Fixed ReDoS vulnerability (switched to comma-split approach)
[1.0.3rc4] - 2025-12-09
Added
- 22 new SQLite wrapper functions
- Schema management:
drop_table(),drop_index(),alter_table_add_column(),get_table_schema(),list_indexes() - Data operations:
sql_insert(),sql_update(),sql_delete(),upsert(),count(),exists() - Query extensions:
query_with_pagination()(with offset/group_by support) - Utilities:
vacuum(),get_db_size(),export_table_to_dict(),import_from_dict_list(),get_last_insert_rowid(),pragma() - Transactions:
begin_transaction(),commit(),rollback(),transaction()context manager
- Schema management:
- 35 new test cases (all passing)
- Complete backward compatibility maintained
[1.0.3rc3] - 2025-12-09
Added
- Pydantic compatibility
set_model(),get_model()methods- Support for nested models and optional fields
- Direct SQL execution
execute(),execute_many(),fetch_one(),fetch_all()methods- SQL injection protection via parameter binding
- SQLite wrapper functions
create_table(),create_index(),query()methodstable_exists(),list_tables()helper functions
- 32 new test cases
- Updated English/Japanese documentation
- Async support consultation document
[1.0.0] - 2025-12-09
Added
- Initial release
- Dict-like interface (
db["key"] = value) - Instant persistence to SQLite via APSW
- Lazy load (on-access) caching
- Bulk load (
bulk_load=True) for startup loading - Nested structure support (tested up to 30 levels)
- Performance optimizations (WAL, mmap, cache_size)
- Batch operations (
batch_update,batch_delete) - Context manager support
- Full dict method compatibility
- Type hints (PEP 561)
- Bilingual documentation (English/Japanese)
- GitHub Actions CI (Python 3.9-3.13, Ubuntu/Windows/macOS)