Skip to content

Commit 3c35e0f

Browse files
feat: Implement caching mechanism for database queries
- Added CacheManager to manage caching of query results. - Introduced QueryCache for storing cached results with eviction policies. - Implemented QueryNormalizer to standardize SQL queries for consistent caching. - Updated BaseDatabaseManager to utilize caching in query execution. - Enhanced ClickHouseManager to handle caching with specific configurations. - Added IPC handlers for cache management in the main process. - Created CacheMonitor component for monitoring cache statistics and configuration in the UI. - Added types for cache entries, metadata, configuration, and statistics.
1 parent 065959b commit 3c35e0f

File tree

12 files changed

+1185
-23
lines changed

12 files changed

+1185
-23
lines changed

src/main/cache/CacheManager.ts

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import { QueryCache } from './QueryCache'
2+
import { QueryNormalizer } from './QueryNormalizer'
3+
import { QueryResult } from '../database/interface'
4+
import { CacheConfig, CacheStats, CacheMetadata, DEFAULT_CACHE_CONFIG } from './types'
5+
6+
export class CacheManager {
7+
private static instance: CacheManager | null = null
8+
private queryCache: QueryCache
9+
private config: CacheConfig
10+
private enabled: boolean = true
11+
12+
private constructor(config: Partial<CacheConfig> = {}) {
13+
this.config = { ...DEFAULT_CACHE_CONFIG, ...config }
14+
this.queryCache = new QueryCache(this.config)
15+
}
16+
17+
static getInstance(config?: Partial<CacheConfig>): CacheManager {
18+
if (!CacheManager.instance) {
19+
CacheManager.instance = new CacheManager(config)
20+
}
21+
return CacheManager.instance
22+
}
23+
24+
/**
25+
* Get cached query result
26+
*/
27+
async getCachedResult(
28+
query: string,
29+
connectionId: string,
30+
database?: string
31+
): Promise<QueryResult | null> {
32+
if (!this.enabled || !QueryNormalizer.shouldCache(query)) {
33+
return null
34+
}
35+
36+
const cacheKey = QueryNormalizer.generateCacheKey(query, connectionId, database)
37+
return await this.queryCache.get(cacheKey)
38+
}
39+
40+
/**
41+
* Cache query result
42+
*/
43+
async cacheResult(
44+
query: string,
45+
result: QueryResult,
46+
connectionId: string,
47+
database?: string
48+
): Promise<void> {
49+
if (!this.enabled || !QueryNormalizer.shouldCache(query)) {
50+
return
51+
}
52+
53+
// Don't cache failed queries
54+
if (!result.success) {
55+
return
56+
}
57+
58+
const normalizedQuery = QueryNormalizer.normalize(query)
59+
const cacheKey = QueryNormalizer.generateCacheKey(query, connectionId, database)
60+
61+
const metadata: CacheMetadata = {
62+
connectionId,
63+
originalQuery: query,
64+
queryHash: normalizedQuery.hash,
65+
hitCount: 0,
66+
lastAccessed: Date.now(),
67+
tables: normalizedQuery.tables,
68+
queryType: normalizedQuery.queryType
69+
}
70+
71+
await this.queryCache.set(cacheKey, result, metadata)
72+
}
73+
74+
/**
75+
* Invalidate cache entries by table name
76+
*/
77+
invalidateTable(tableName: string): number {
78+
console.log(`Invalidating cache for table: ${tableName}`)
79+
return this.queryCache.invalidateByTable(tableName)
80+
}
81+
82+
/**
83+
* Invalidate cache entries by connection
84+
*/
85+
invalidateConnection(connectionId: string): number {
86+
console.log(`Invalidating cache for connection: ${connectionId}`)
87+
return this.queryCache.invalidateByConnection(connectionId)
88+
}
89+
90+
/**
91+
* Invalidate cache based on query type
92+
*/
93+
invalidateByQueryType(query: string, connectionId: string, _database?: string): void {
94+
const normalizedQuery = QueryNormalizer.normalize(query)
95+
96+
switch (normalizedQuery.queryType) {
97+
case 'DDL':
98+
// DDL operations might affect schema, invalidate all for this connection
99+
this.invalidateConnection(connectionId)
100+
break
101+
102+
case 'DML':
103+
// DML operations affect specific tables
104+
normalizedQuery.tables.forEach((table) => {
105+
this.invalidateTable(table)
106+
})
107+
break
108+
109+
default:
110+
// No invalidation needed for SELECT queries
111+
break
112+
}
113+
}
114+
115+
/**
116+
* Clear all cache entries
117+
*/
118+
clearAll(): void {
119+
console.log('Clearing all cache entries')
120+
this.queryCache.clear()
121+
}
122+
123+
/**
124+
* Get cache statistics
125+
*/
126+
getStats(): CacheStats {
127+
return this.queryCache.getStats()
128+
}
129+
130+
/**
131+
* Get cache configuration
132+
*/
133+
getConfig(): CacheConfig {
134+
return { ...this.config }
135+
}
136+
137+
/**
138+
* Update cache configuration
139+
*/
140+
updateConfig(newConfig: Partial<CacheConfig>): void {
141+
this.config = { ...this.config, ...newConfig }
142+
this.queryCache.updateConfig(this.config)
143+
}
144+
145+
/**
146+
* Enable or disable caching
147+
*/
148+
setEnabled(enabled: boolean): void {
149+
this.enabled = enabled
150+
if (!enabled) {
151+
this.clearAll()
152+
}
153+
console.log(`Query caching ${enabled ? 'enabled' : 'disabled'}`)
154+
}
155+
156+
/**
157+
* Check if caching is enabled
158+
*/
159+
isEnabled(): boolean {
160+
return this.enabled
161+
}
162+
163+
/**
164+
* Get cache entry details for debugging
165+
*/
166+
getCacheEntryInfo(query: string, connectionId: string, database?: string): any {
167+
const cacheKey = QueryNormalizer.generateCacheKey(query, connectionId, database)
168+
const normalizedQuery = QueryNormalizer.normalize(query)
169+
170+
return {
171+
cacheKey,
172+
normalizedQuery,
173+
shouldCache: QueryNormalizer.shouldCache(query),
174+
isEnabled: this.enabled
175+
}
176+
}
177+
178+
/**
179+
* Dispose of cache manager and clean up resources
180+
*/
181+
dispose(): void {
182+
this.queryCache.dispose()
183+
CacheManager.instance = null
184+
}
185+
186+
/**
187+
* Export cache statistics for monitoring
188+
*/
189+
exportMetrics(): any {
190+
const stats = this.getStats()
191+
const config = this.getConfig()
192+
193+
return {
194+
timestamp: new Date().toISOString(),
195+
enabled: this.enabled,
196+
stats,
197+
config: {
198+
maxSize: config.maxSize,
199+
maxMemory: config.maxMemory,
200+
defaultTTL: config.defaultTTL,
201+
compressionThreshold: config.compressionThreshold,
202+
enableCompression: config.enableCompression
203+
},
204+
memoryUsage: {
205+
used: stats.totalMemory,
206+
limit: config.maxMemory,
207+
utilization: stats.totalMemory / config.maxMemory
208+
},
209+
performance: {
210+
hitRatio: stats.hitRatio,
211+
totalQueries: stats.hits + stats.misses,
212+
cacheEfficiency: stats.hits > 0 ? stats.hits / (stats.hits + stats.misses) : 0
213+
}
214+
}
215+
}
216+
}

0 commit comments

Comments
 (0)