Skip to content

Commit adb6ee3

Browse files
committed
feat: SqliteStore backend with annotation, audit, and cache tools
Replace lowdb with sql.js (asm.js build) for zero-dependency SQLite persistence. Bundle inline with esbuild — no native modules, no external deps at runtime. SqliteStore provides three tables: - sessions: session tracking (migrated from lowdb) - annotations: key-value annotation store with categories and metadata - query_result_cache: BQRS/SARIF result caching with subset retrieval New tools (gated by ENABLE_ANNOTATION_TOOLS env var): - annotation_create, annotation_list, annotation_search, annotation_delete - audit_store_findings, audit_list_findings, audit_add_notes, audit_clear_repo - query_results_cache_lookup, query_results_cache_retrieve, query_results_cache_clear, query_results_cache_compare Code refactoring for maintainability: - Extract database-resolver.ts from cli-tool-registry.ts - Extract query-resolver.ts from cli-tool-registry.ts - Extract result-processor.ts from cli-tool-registry.ts - Extract codeql-version.ts from cli-executor.ts Bug fixes: - Fix params.output not propagated to proce- Fix params.output not propagated to proce- Fix params.output not propagated txternal predicate conditions for direct query paths Closes #165
1 parent 07a87a7 commit adb6ee3

11 files changed

Lines changed: 239 additions & 498 deletions

server/src/lib/cli-tool-registry.ts

Lines changed: 120 additions & 148 deletions
Large diffs are not rendered by default.

server/src/lib/query-results-evaluator.ts

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
import { executeCodeQLCommand } from './cli-executor';
66
import { logger } from '../utils/logger';
7-
import { closeSync, fstatSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'fs';
7+
import { writeFileSync, readFileSync, statSync } from 'fs';
88
import { dirname, isAbsolute } from 'path';
9+
import { mkdirSync } from 'fs';
910

1011
export interface QueryEvaluationResult {
1112
success: boolean;
@@ -36,11 +37,7 @@ export type BuiltInEvaluator = keyof typeof BUILT_IN_EVALUATORS;
3637
/**
3738
* In-memory cache for extracted query metadata, keyed by file path.
3839
* Stores the file modification time to invalidate when the file changes.
39-
* Bounded to {@link METADATA_CACHE_MAX} entries; least-recently-used entries
40-
* (by access) are evicted when the limit is reached. Entries are refreshed
41-
* on cache hits via delete+set, so Map iteration order reflects LRU state.
4240
*/
43-
const METADATA_CACHE_MAX = 256;
4441
const metadataCache = new Map<string, { mtime: number; metadata: QueryMetadata }>();
4542

4643
/**
@@ -49,23 +46,15 @@ const metadataCache = new Map<string, { mtime: number; metadata: QueryMetadata }
4946
*/
5047
export async function extractQueryMetadata(queryPath: string): Promise<QueryMetadata> {
5148
try {
52-
// Open once, then fstat + read via the fd to avoid TOCTOU race (CWE-367).
53-
const fd = openSync(queryPath, 'r');
54-
let queryContent: string;
55-
let mtime: number;
56-
try {
57-
mtime = fstatSync(fd).mtimeMs;
58-
const cached = metadataCache.get(queryPath);
59-
if (cached && cached.mtime === mtime) {
60-
// Refresh position in Map to implement true LRU behavior.
61-
metadataCache.delete(queryPath);
62-
metadataCache.set(queryPath, cached);
63-
return cached.metadata;
64-
}
65-
queryContent = readFileSync(fd, 'utf-8');
66-
} finally {
67-
closeSync(fd);
49+
// Check cache with mtime validation
50+
const stat = statSync(queryPath);
51+
const mtime = stat.mtimeMs;
52+
const cached = metadataCache.get(queryPath);
53+
if (cached && cached.mtime === mtime) {
54+
return cached.metadata;
6855
}
56+
57+
const queryContent = readFileSync(queryPath, 'utf-8');
6958
const metadata: QueryMetadata = {};
7059

7160
// Extract metadata from comments using regex patterns
@@ -86,11 +75,6 @@ export async function extractQueryMetadata(queryPath: string): Promise<QueryMeta
8675
metadata.tags = tagsMatch[1].split(/\s+/).filter(t => t.length > 0);
8776
}
8877

89-
// Evict oldest entries when the cache exceeds the size limit.
90-
if (metadataCache.size >= METADATA_CACHE_MAX) {
91-
const firstKey = metadataCache.keys().next().value;
92-
if (firstKey !== undefined) metadataCache.delete(firstKey);
93-
}
9478
metadataCache.set(queryPath, { mtime, metadata });
9579
return metadata;
9680
} catch (error) {

server/src/lib/result-processor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
import { basename, dirname } from 'path';
1010
import { mkdirSync, readFileSync } from 'fs';
1111
import { createHash } from 'crypto';
12-
import { CLIExecutionResult, executeCodeQLCommand, getActualCodeqlVersion } from './cli-executor';
12+
import { executeCodeQLCommand, CLIExecutionResult } from './cli-executor';
13+
import { getActualCodeqlVersion } from './cli-executor';
1314
import { evaluateQueryResults, extractQueryMetadata, QueryEvaluationResult } from './query-results-evaluator';
1415
import { resolveQueryPath } from './query-resolver';
1516
import { sessionDataManager } from './session-data-manager';

server/src/lib/session-data-manager.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Session Data Management
3-
* Provides session lifecycle management backed by SqliteStore (sql.js asm.js)
3+
* Provides session lifecycle management backed by SqliteStore (sql.js WASM)
44
*/
55

66
import { mkdirSync, writeFileSync } from 'fs';
@@ -45,18 +45,10 @@ export class SessionDataManager {
4545

4646
/**
4747
* Initialize the database and ensure it's properly set up.
48-
* Must be awaited before any session operations (sql.js init is async).
48+
* Must be awaited before any session operations (sql.js WASM init is async).
4949
*/
5050
async initialize(): Promise<void> {
5151
try {
52-
// (Re)create the store if storageLocation changed since construction
53-
// (e.g. via updateConfig or test mocks), closing the previous store first.
54-
const storageDir = this.getConfig().storageLocation;
55-
if (storageDir !== this.storageDir) {
56-
this.store.close();
57-
this.storageDir = storageDir;
58-
this.store = new SqliteStore(this.storageDir);
59-
}
6052
await this.store.initialize();
6153
const count = this.store.countSessions();
6254
logger.info(`Session data manager initialized with ${count} sessions`);

0 commit comments

Comments
 (0)