diff --git a/package-lock.json b/package-lock.json index 2c0786b..f65ac18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tidesdb", - "version": "0.5.4", + "version": "0.5.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tidesdb", - "version": "0.5.3", + "version": "0.5.5", "license": "MPL-2.0", "dependencies": { "koffi": "^2.9.0" diff --git a/package.json b/package.json index 9224caa..93b19c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tidesdb", - "version": "0.5.4", + "version": "0.5.5", "description": "TypeScript bindings for TidesDB - A high-performance embedded key-value storage engine", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/column-family.ts b/src/column-family.ts index c9de95c..90f369b 100644 --- a/src/column-family.ts +++ b/src/column-family.ts @@ -29,6 +29,7 @@ import { CommitOpStruct, commitHookPtrType, StatsStruct, + ColumnFamilyConfigStruct, } from './ffi'; import { checkResult } from './error'; import { Stats, ColumnFamilyConfig, CompressionAlgorithm, SyncMode, IsolationLevel, CommitOp, CommitHookCallback } from './types'; @@ -117,6 +118,38 @@ export class ColumnFamily { } } + // Decode config from the stats struct + let config: ColumnFamilyConfig | undefined; + if (decoded.config) { + try { + const cfgDecoded = koffi.decode(decoded.config, ColumnFamilyConfigStruct) as Record; + config = { + writeBufferSize: cfgDecoded.write_buffer_size as number, + levelSizeRatio: cfgDecoded.level_size_ratio as number, + minLevels: cfgDecoded.min_levels as number, + dividingLevelOffset: cfgDecoded.dividing_level_offset as number, + klogValueThreshold: cfgDecoded.klog_value_threshold as number, + compressionAlgorithm: cfgDecoded.compression_algorithm as CompressionAlgorithm, + enableBloomFilter: (cfgDecoded.enable_bloom_filter as number) !== 0, + bloomFpr: cfgDecoded.bloom_fpr as number, + enableBlockIndexes: (cfgDecoded.enable_block_indexes as number) !== 0, + indexSampleRatio: cfgDecoded.index_sample_ratio as number, + blockIndexPrefixLen: cfgDecoded.block_index_prefix_len as number, + syncMode: cfgDecoded.sync_mode as SyncMode, + syncIntervalUs: cfgDecoded.sync_interval_us as number, + skipListMaxLevel: cfgDecoded.skip_list_max_level as number, + skipListProbability: cfgDecoded.skip_list_probability as number, + defaultIsolationLevel: cfgDecoded.default_isolation_level as IsolationLevel, + minDiskSpace: cfgDecoded.min_disk_space as number, + l1FileCountTrigger: cfgDecoded.l1_file_count_trigger as number, + l0QueueStallThreshold: cfgDecoded.l0_queue_stall_threshold as number, + useBtree: (cfgDecoded.use_btree as number) !== 0, + }; + } catch { + // If decoding fails, config remains undefined + } + } + tidesdb_free_stats(statsPtr); return { @@ -124,6 +157,7 @@ export class ColumnFamily { memtableSize, levelSizes, levelNumSSTables, + config, totalKeys, totalDataSize, avgKeySize, diff --git a/src/ffi.ts b/src/ffi.ts index e9ad1a0..3cbd775 100644 --- a/src/ffi.ts +++ b/src/ffi.ts @@ -40,6 +40,7 @@ export const TidesDBConfigStruct = koffi.struct('tidesdb_config_t', { log_level: 'int', block_cache_size: 'size_t', max_open_sstables: 'size_t', + max_memory_usage: 'size_t', log_to_file: 'int', log_truncation_at: 'size_t', }); diff --git a/src/tidesdb.test.ts b/src/tidesdb.test.ts index dd7855a..b96a1a8 100644 --- a/src/tidesdb.test.ts +++ b/src/tidesdb.test.ts @@ -630,6 +630,40 @@ describe('TidesDB', () => { }); }); + describe('Custom Comparators', () => { + test('getComparator returns true for built-in comparators', () => { + expect(db.getComparator('memcmp')).toBe(true); + expect(db.getComparator('reverse')).toBe(true); + expect(db.getComparator('lexicographic')).toBe(true); + expect(db.getComparator('uint64')).toBe(true); + expect(db.getComparator('int64')).toBe(true); + expect(db.getComparator('case_insensitive')).toBe(true); + }); + + test('getComparator returns false for non-existent comparator', () => { + expect(db.getComparator('nonexistent_comparator')).toBe(false); + }); + }); + + describe('Stats Config', () => { + test('getStats returns config field', () => { + db.createColumnFamily('stats_cfg_cf', { + compressionAlgorithm: CompressionAlgorithm.Lz4Compression, + enableBloomFilter: true, + bloomFpr: 0.01, + }); + const cf = db.getColumnFamily('stats_cfg_cf'); + + const stats = cf.getStats(); + expect(stats.config).toBeDefined(); + if (stats.config) { + expect(typeof stats.config.writeBufferSize).toBe('number'); + expect(stats.config.enableBloomFilter).toBe(true); + expect(stats.config.compressionAlgorithm).toBe(CompressionAlgorithm.Lz4Compression); + } + }); + }); + describe('Range Cost Estimation', () => { test('rangeCost returns a number', () => { db.createColumnFamily('range_cf'); diff --git a/src/tidesdb.ts b/src/tidesdb.ts index 46602a6..67b899c 100644 --- a/src/tidesdb.ts +++ b/src/tidesdb.ts @@ -29,6 +29,7 @@ import { tidesdb_txn_begin, tidesdb_txn_begin_with_isolation, tidesdb_register_comparator, + tidesdb_get_comparator, tidesdb_get_cache_stats, tidesdb_backup, tidesdb_checkpoint, @@ -60,6 +61,7 @@ export function defaultConfig(): Partial { logLevel: LogLevel.Info, blockCacheSize: 64 * 1024 * 1024, maxOpenSSTables: 256, + maxMemoryUsage: 0, logToFile: false, logTruncationAt: 24 * 1024 * 1024, }; @@ -118,6 +120,7 @@ export class TidesDB { log_level: mergedConfig.logLevel!, block_cache_size: mergedConfig.blockCacheSize!, max_open_sstables: mergedConfig.maxOpenSSTables!, + max_memory_usage: mergedConfig.maxMemoryUsage!, log_to_file: mergedConfig.logToFile ? 1 : 0, log_truncation_at: mergedConfig.logTruncationAt!, }; @@ -324,6 +327,21 @@ export class TidesDB { checkResult(result, 'failed to register comparator'); } + /** + * Retrieve a registered comparator by name. + * Returns true if the comparator is registered, false otherwise. + * @param name Name of the comparator to look up. + */ + getComparator(name: string): boolean { + if (!this._db) throw new Error('Database has been closed'); + + const fnPtrOut: unknown[] = [null]; + const ctxPtrOut: unknown[] = [null]; + + const result = tidesdb_get_comparator(this._db, name, fnPtrOut, ctxPtrOut); + return result === 0; + } + /** * Create an on-disk backup of the database without blocking reads/writes. * @param dir Backup directory path (must be non-existent or empty). diff --git a/src/types.ts b/src/types.ts index a77a09c..d013c4f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -93,6 +93,8 @@ export interface Config { blockCacheSize?: number; /** Maximum number of open SSTables. Default: 256 */ maxOpenSSTables?: number; + /** Global memory limit in bytes. Default: 0 (auto, 50% of system RAM). */ + maxMemoryUsage?: number; /** Write logs to file instead of stderr. Default: false */ logToFile?: boolean; /** Log file truncation size in bytes. Default: 24MB, 0 = no truncation */