8.2 KiB
8.2 KiB
Changelog
All notable changes to this project will be documented in this file.
[Unreleased]
[1.4.0] - 2026-06-05
Fixed
decimal.Decimal(anddatetime) binding error —NUMERIC/DECIMAL/MONEYcolumns from SQL Server (pyodbc) arrive asdecimal.Decimal, whichsqlite3cannot bind, crashing the cache load withtype 'decimal.Decimal' is not supported. Values are now coerced to sqlite-bindable types (Decimal→str,datetime/date/time→ISO,uuid.UUID→str,bytearray→bytes) at the cache boundary — on full load, on delta upsert, and for WHERE parameters. Coercion is local (no globalsqlite3.register_adapter), so the host application'ssqlite3behaviour is untouched. Cache columns areTEXT, so the conversion is lossless and exact (no rounding).
Added
- Incremental (delta) refresh —
CachingEngine(engine, delta={...})withDeltaConfig(change_column, key_columns). Delta-tracked tables are kept in sync by pulling only changed rows (WHERE change_column >= watermark) and upserting them by key, instead of full reloads.- Data-driven high-watermark =
max(change_column)cached, persisted incache.db;>=overlap + idempotent upsert so no row is missed and boundary rows are harmlessly re-read. - Catch-up on startup (since last shutdown) and a background thread refreshing every
SQLMEM_REFRESH_INTERVALseconds (default 300);engine.refresh()triggers a pull on demand. - Primary key is auto-discovered from the source DB (
inspect(engine).get_pk_constraint) whenkey_columnsis omitted; required explicitly for views (raisesValueError).
- Data-driven high-watermark =
- Per-table TTL (time-based refresh) —
CachingEngine(engine, ttl={"VW_X": 300})for tables with no change column that can't be delta-synced. The cached copy is guaranteed never older than the TTL: a query touching an expired table triggers a full reload before it is answered (read-time guarantee), and the background thread proactively reloads expired tables. TTL age uses the persistedlast_refresh_at, so the bound holds across restarts. A table in bothdeltaandttlraisesValueError. DeltaConfigexported from the public API.engine.reset()— wipes the whole cache (RAM +cache.db) for a clean rebuild after structural source changes.SQLMEM_REFRESH_INTERVALenv var (default300) — background refresh tick for delta pulls and proactive TTL reloads.
Changed
pyproject.toml— bumped version to1.4.0cache.py— schema version bumped to3;_sqlmem_tablesgained alast_synced_atwatermark column. New methods:execute_in_memory(lock-serialized read),get_table_columns,create_unique_index,get/set_last_synced_at,max_value,upsert_rows,seconds_since_refresh,reset. Existing on-disk caches are discarded and rebuilt on load.executor.py— delta-tracked tables augment their column set with key/change columns (unique key index + initial watermark); TTL-tracked tables full-reload at read time when expired; in-memory reads go through the cache lock.
[1.2.0] - 2026-06-04
Added
- Parametrized queries (R1) —
execute(sql, params)accepts positional (?tuple/list) and named (:namedict) parameters; passed straight to SQLite during in-memory filtering. Cache loads still fetch the full table (parameters are not applied to source fetches). - JOIN support (R2) — multi-table SELECTs are parsed into per-table column sets; each table is cached independently and the JOIN runs in the in-memory SQLite. Columns in a multi-table query must be qualified by table or alias.
SELECT *support (R3) — wildcard (andalias.*) queries discover all columns from the source DB, cache the whole table, and mark itis_fullso later column queries are guaranteed cache hits without re-fetch.- Three-part table names (R4) —
[catalog].[schema].[table]is parsed to its base name for caching; the in-memory query is rewritten to strip catalog/schema prefixes so it runs under SQLite. SQLMEM_SQL_DIALECTenv var (defaulttsql) — sqlglot dialect used to parse incoming SQL; T-SQL also accepts ANSI SQL and MSSQL bracket quoting.CacheManager.discover_columns()andCacheManager.is_table_full();load_table()gained afullflag.
Changed
pyproject.toml— bumped version to1.2.0parser.py—ParsedQuery.table: strreplaced bytables: list[str]pluscolumns_by_table,sqlite_sql,params, andwildcard_tables; SQL is parsed with the configured dialect and rendered to SQLite for execution.executor.py— loads each referenced table independently and applies query parameters during in-memory execution.cache.py— schema version bumped to2;_sqlmem_tablesgained anis_fullcolumn (existing on-disk caches are discarded and rebuilt on load).
[1.1.0] - 2026-06-03
Added
StatsandTableStatsfrozen dataclasses — snapshot of runtime cache statistics (hit/miss/refetch counts, per-table row count, columns, last refresh timestamp)StatsCollector— internal thread-safe counter; increments on every cache hit, miss, and re-fetchengine.statsproperty — returns aStatssnapshot at any point in timeStatsandTableStatsexported from the public API
Changed
pyproject.toml— bumped version to1.1.0
[1.0.0] - 2026-06-03
Changed
pyproject.toml— bumped version to1.0.0
[0.4.0] - 2026-06-03
Added
add_sink(sink, *, level, **kwargs)— public API for routing sqlmem log records to any loguru-compatible sink (stream, file, callable); supports all logurulogger.add()kwargs includingrotation,retention, etc.
Changed
pyproject.toml— bumped version to0.4.0config.py— replaced destructivelogger.remove()+ forced default sink withlogger.disable("sqlmem"); sqlmem is now silent by default and does not interfere with the host application's logging setup
[0.3.0] - 2026-06-03
Added
README.md— full project documentation: architecture overview, quick start, cache behaviour, persistence, configuration, exceptions, logging, and limitations
Changed
pyproject.toml— bumped version to0.3.0parser.py—_extract_columnsnow deduplicates column names while preserving order.gitignore— added.envand.env.*to prevent accidental commit of environment files
Security
- Removed
.envfrom git tracking (git rm --cached)
[0.2.0] - 2026-06-01
Added
- Project specification in
project.md— architecture, API design, cache backend, metadata schema, logging strategy, and TODO for future features (JOIN, SELECT * support) .gitignorefor Python/Poetry projectpyproject.tomldependencies:sqlglot,sqlalchemy,loguru,python-dotenv; dev dependencies:pytest,ruff,mypysrc/sqlmem/package structure with src layoutsrc/sqlmem/exceptions.py—ReadOnlyError(blocks INSERT/UPDATE/DELETE),UnsupportedQueryError(blocks JOIN and SELECT *)src/sqlmem/config.py— loads.env, configuresloguruwith DEBUG/INFO level based onSQLMEM_DEBUGsrc/sqlmem/_meta.py— package version constantsrc/sqlmem/parser.py— SQL Parser usingsqlglot; extracts table and columns from SELECT, raises on writes/JOIN/wildcardsrc/sqlmem/registry.py— Column Registry; accumulates requested columns per table, detects missing columns requiring re-fetchsrc/sqlmem/cache.py— Cache Manager; SQLite in-memory storage, load fromcache.dbon startup (with schema version check), hourly backup thread,atexit/SIGTERM flush, metadata tables (_sqlmem_meta,_sqlmem_tables,_sqlmem_columns)src/sqlmem/executor.py— Query Executor; cache hit/miss logic, re-fetch on new columns with WARNING logsrc/sqlmem/engine.py—CachingEnginewrapper; public API compatible with SQLAlchemy,invalidate(table)for manual cache clearingsrc/sqlmem/__init__.py— public exports:CachingEngine,ReadOnlyError,UnsupportedQueryErrortests/test_parser.py— parser tests: SELECT parsing, ReadOnlyError, UnsupportedQueryErrortests/test_cache.py— cache tests: load, data correctness, metadata, disk backup/reloadtests/test_registry.py— registry tests: accumulation, needs_refetch, table isolation