Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
cache_test.go18.7 kB
// Package cypher - Query cache tests. package cypher import ( "sync" "testing" "time" ) func TestQueryCache_Basic(t *testing.T) { cache := NewQueryCache(10) result := &ExecuteResult{ Columns: []string{"n"}, Rows: [][]interface{}{{"value"}}, } // Cache miss _, found := cache.Get("MATCH (n) RETURN n", nil) if found { t.Error("Expected cache miss, got hit") } // Store result cache.Put("MATCH (n) RETURN n", nil, result, 1*time.Second) // Cache hit cached, found := cache.Get("MATCH (n) RETURN n", nil) if !found { t.Error("Expected cache hit, got miss") } if len(cached.Rows) != 1 || cached.Rows[0][0] != "value" { t.Error("Cached result doesn't match original") } } func TestQueryCache_TTL(t *testing.T) { cache := NewQueryCache(10) result := &ExecuteResult{ Columns: []string{"n"}, Rows: [][]interface{}{{"value"}}, } // Store with very short TTL cache.Put("MATCH (n) RETURN n", nil, result, 1*time.Millisecond) // Immediate hit _, found := cache.Get("MATCH (n) RETURN n", nil) if !found { t.Error("Expected cache hit immediately after put") } // Wait for TTL to expire time.Sleep(5 * time.Millisecond) // Should be expired _, found = cache.Get("MATCH (n) RETURN n", nil) if found { t.Error("Expected cache miss after TTL expiration") } } func TestQueryCache_LRUEviction(t *testing.T) { cache := NewQueryCache(3) // Small cache result := &ExecuteResult{Columns: []string{"n"}} // Fill cache cache.Put("query1", nil, result, 1*time.Minute) cache.Put("query2", nil, result, 1*time.Minute) cache.Put("query3", nil, result, 1*time.Minute) // All should be cached _, found := cache.Get("query1", nil) if !found { t.Error("query1 should be cached") } // Add fourth item - should evict query2 (least recently used) cache.Put("query4", nil, result, 1*time.Minute) // query2 should be evicted _, found = cache.Get("query2", nil) if found { t.Error("query2 should have been evicted") } // query1, query3, query4 should still be there _, found = cache.Get("query1", nil) if !found { t.Error("query1 should still be cached") } _, found = cache.Get("query3", nil) if !found { t.Error("query3 should still be cached") } _, found = cache.Get("query4", nil) if !found { t.Error("query4 should be cached") } } func TestQueryCache_ParameterizedQueries(t *testing.T) { cache := NewQueryCache(10) result1 := &ExecuteResult{Rows: [][]interface{}{{"Alice"}}} result2 := &ExecuteResult{Rows: [][]interface{}{{"Bob"}}} params1 := map[string]interface{}{"name": "Alice"} params2 := map[string]interface{}{"name": "Bob"} // Cache same query with different params cache.Put("MATCH (n {name: $name}) RETURN n", params1, result1, 1*time.Minute) cache.Put("MATCH (n {name: $name}) RETURN n", params2, result2, 1*time.Minute) // Should retrieve correct results for each param set cached1, found := cache.Get("MATCH (n {name: $name}) RETURN n", params1) if !found || cached1.Rows[0][0] != "Alice" { t.Error("Should retrieve Alice result") } cached2, found := cache.Get("MATCH (n {name: $name}) RETURN n", params2) if !found || cached2.Rows[0][0] != "Bob" { t.Error("Should retrieve Bob result") } } func TestQueryCache_Invalidate(t *testing.T) { cache := NewQueryCache(10) result := &ExecuteResult{Columns: []string{"n"}} // Cache multiple queries cache.Put("query1", nil, result, 1*time.Minute) cache.Put("query2", nil, result, 1*time.Minute) cache.Put("query3", nil, result, 1*time.Minute) // Verify cached _, found := cache.Get("query1", nil) if !found { t.Error("query1 should be cached before invalidation") } // Invalidate entire cache cache.Invalidate() // All should be gone _, found = cache.Get("query1", nil) if found { t.Error("query1 should be invalidated") } _, found = cache.Get("query2", nil) if found { t.Error("query2 should be invalidated") } _, found = cache.Get("query3", nil) if found { t.Error("query3 should be invalidated") } } func TestQueryCache_Stats(t *testing.T) { cache := NewQueryCache(10) result := &ExecuteResult{Columns: []string{"n"}} // Initial stats hits, misses, size := cache.Stats() if hits != 0 || misses != 0 || size != 0 { t.Errorf("Initial stats wrong: hits=%d, misses=%d, size=%d", hits, misses, size) } // Cache miss cache.Get("query1", nil) hits, misses, size = cache.Stats() if hits != 0 || misses != 1 { t.Errorf("After miss: hits=%d, misses=%d", hits, misses) } // Add to cache cache.Put("query1", nil, result, 1*time.Minute) hits, misses, size = cache.Stats() if size != 1 { t.Errorf("Cache size should be 1, got %d", size) } // Cache hit cache.Get("query1", nil) hits, misses, size = cache.Stats() if hits != 1 || misses != 1 { t.Errorf("After hit: hits=%d, misses=%d", hits, misses) } } // ============================================================================= // SMART QUERY CACHE TESTS // ============================================================================= func TestSmartQueryCache_Basic(t *testing.T) { cache := NewSmartQueryCache(10) result := &ExecuteResult{ Columns: []string{"n"}, Rows: [][]interface{}{{"value"}}, } // Cache miss _, found := cache.Get("MATCH (n:User) RETURN n", nil) if found { t.Error("Expected cache miss, got hit") } // Store result cache.Put("MATCH (n:User) RETURN n", nil, result, 1*time.Second) // Cache hit cached, found := cache.Get("MATCH (n:User) RETURN n", nil) if !found { t.Error("Expected cache hit, got miss") } if len(cached.Rows) != 1 || cached.Rows[0][0] != "value" { t.Error("Cached result doesn't match original") } } func TestSmartQueryCache_LabelExtraction(t *testing.T) { tests := []struct { name string query string expected []string }{ { name: "single label", query: "MATCH (n:User) RETURN n", expected: []string{"User"}, }, { name: "multiple labels", query: "MATCH (n:User)-[:KNOWS]->(m:Person) RETURN n, m", expected: []string{"User", "KNOWS", "Person"}, }, { name: "no labels", query: "MATCH (n) RETURN n", expected: nil, }, { name: "complex query", query: "MATCH (u:User)-[:FOLLOWS]->(p:Post)-[:HAS_TAG]->(t:Tag) WHERE u.name = 'Alice' RETURN p", expected: []string{"User", "FOLLOWS", "Post", "HAS_TAG", "Tag"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { labels := extractLabelsFromQuery(tt.query) if len(labels) != len(tt.expected) { t.Errorf("Expected %d labels, got %d: %v", len(tt.expected), len(labels), labels) return } for i, label := range labels { if label != tt.expected[i] { t.Errorf("Expected label[%d]=%s, got %s", i, tt.expected[i], label) } } }) } } func TestSmartQueryCache_SmartInvalidation(t *testing.T) { cache := NewSmartQueryCache(10) userResult := &ExecuteResult{Rows: [][]interface{}{{"user-data"}}} productResult := &ExecuteResult{Rows: [][]interface{}{{"product-data"}}} // Cache queries for different labels cache.PutWithLabels("MATCH (n:User) RETURN n", nil, userResult, 1*time.Minute, []string{"User"}) cache.PutWithLabels("MATCH (n:Product) RETURN n", nil, productResult, 1*time.Minute, []string{"Product"}) // Both should be cached _, found := cache.Get("MATCH (n:User) RETURN n", nil) if !found { t.Error("User query should be cached") } _, found = cache.Get("MATCH (n:Product) RETURN n", nil) if !found { t.Error("Product query should be cached") } // Invalidate only User label cache.InvalidateLabels([]string{"User"}) // User query should be invalidated _, found = cache.Get("MATCH (n:User) RETURN n", nil) if found { t.Error("User query should be invalidated") } // Product query should still be cached cached, found := cache.Get("MATCH (n:Product) RETURN n", nil) if !found { t.Error("Product query should still be cached") } if cached.Rows[0][0] != "product-data" { t.Error("Product data should be preserved") } } func TestSmartQueryCache_FullInvalidation(t *testing.T) { cache := NewSmartQueryCache(10) result := &ExecuteResult{Columns: []string{"n"}} // Cache multiple queries cache.PutWithLabels("query1", nil, result, 1*time.Minute, []string{"Label1"}) cache.PutWithLabels("query2", nil, result, 1*time.Minute, []string{"Label2"}) cache.PutWithLabels("query3", nil, result, 1*time.Minute, []string{"Label3"}) // Full invalidation cache.Invalidate() // All should be gone _, found := cache.Get("query1", nil) if found { t.Error("query1 should be invalidated") } _, found = cache.Get("query2", nil) if found { t.Error("query2 should be invalidated") } _, found = cache.Get("query3", nil) if found { t.Error("query3 should be invalidated") } } func TestSmartQueryCache_TTL(t *testing.T) { cache := NewSmartQueryCache(10) result := &ExecuteResult{Rows: [][]interface{}{{"value"}}} // Store with very short TTL cache.PutWithLabels("query", nil, result, 1*time.Millisecond, []string{"Test"}) // Immediate hit _, found := cache.Get("query", nil) if !found { t.Error("Expected cache hit immediately after put") } // Wait for TTL to expire time.Sleep(5 * time.Millisecond) // Should be expired _, found = cache.Get("query", nil) if found { t.Error("Expected cache miss after TTL expiration") } } func TestSmartQueryCache_LRUEviction(t *testing.T) { cache := NewSmartQueryCache(3) // Small cache result := &ExecuteResult{Columns: []string{"n"}} // Fill cache cache.PutWithLabels("query1", nil, result, 1*time.Minute, []string{"L1"}) cache.PutWithLabels("query2", nil, result, 1*time.Minute, []string{"L2"}) cache.PutWithLabels("query3", nil, result, 1*time.Minute, []string{"L3"}) // Access query1 to make it recently used cache.Get("query1", nil) // Add fourth item - should evict query2 (least recently used) cache.PutWithLabels("query4", nil, result, 1*time.Minute, []string{"L4"}) // query2 should be evicted _, found := cache.Get("query2", nil) if found { t.Error("query2 should have been evicted") } // query1, query3, query4 should still be there _, found = cache.Get("query1", nil) if !found { t.Error("query1 should still be cached") } } func TestSmartQueryCache_Stats(t *testing.T) { cache := NewSmartQueryCache(10) result := &ExecuteResult{Columns: []string{"n"}} // Initial stats hits, misses, size, smartInvals, fullInvals := cache.Stats() if hits != 0 || misses != 0 || size != 0 || smartInvals != 0 || fullInvals != 0 { t.Errorf("Initial stats wrong") } // Cache miss cache.Get("query1", nil) hits, misses, size, _, _ = cache.Stats() if misses != 1 { t.Errorf("Expected 1 miss, got %d", misses) } // Add and hit cache.PutWithLabels("query1", nil, result, 1*time.Minute, []string{"Label"}) cache.Get("query1", nil) hits, _, size, _, _ = cache.Stats() if hits != 1 || size != 1 { t.Errorf("Expected 1 hit and size 1, got hits=%d, size=%d", hits, size) } // Smart invalidation cache.InvalidateLabels([]string{"Label"}) _, _, _, smartInvals, fullInvals = cache.Stats() if smartInvals != 1 { t.Errorf("Expected 1 smart invalidation, got %d", smartInvals) } // Full invalidation cache.PutWithLabels("query2", nil, result, 1*time.Minute, []string{"Label"}) cache.Invalidate() _, _, _, smartInvals, fullInvals = cache.Stats() if fullInvals != 1 { t.Errorf("Expected 1 full invalidation, got %d", fullInvals) } } func TestSmartQueryCache_Concurrent(t *testing.T) { cache := NewSmartQueryCache(100) result := &ExecuteResult{Columns: []string{"n"}} var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(id int) { defer wg.Done() for j := 0; j < 100; j++ { key := "query" + string(rune('A'+id)) cache.Put(key, nil, result, 1*time.Minute) cache.Get(key, nil) if j%10 == 0 { cache.InvalidateLabels([]string{"User"}) } } }(i) } wg.Wait() // Should not panic or deadlock hits, misses, size, _, _ := cache.Stats() t.Logf("Concurrent test: hits=%d, misses=%d, size=%d", hits, misses, size) } // ============================================================================= // QUERY PLAN CACHE TESTS // ============================================================================= func TestQueryPlanCache_Basic(t *testing.T) { cache := NewQueryPlanCache(10) clauses := []Clause{&MatchClause{}} // Cache miss _, _, found := cache.Get("MATCH (n) RETURN n") if found { t.Error("Expected cache miss, got hit") } // Store plan cache.Put("MATCH (n) RETURN n", clauses, QueryMatch) // Cache hit cachedClauses, queryType, found := cache.Get("MATCH (n) RETURN n") if !found { t.Error("Expected cache hit, got miss") } if len(cachedClauses) != 1 { t.Errorf("Expected 1 clause, got %d", len(cachedClauses)) } if queryType != QueryMatch { t.Errorf("Expected QueryMatch, got %v", queryType) } } func TestQueryPlanCache_Normalization(t *testing.T) { cache := NewQueryPlanCache(10) clauses := []Clause{&MatchClause{}} // Store with extra whitespace cache.Put("MATCH (n) RETURN n", clauses, QueryMatch) // Should find with normalized query _, _, found := cache.Get("MATCH (n) RETURN n") if !found { t.Error("Normalized query should hit cache") } // Different whitespace patterns should all hit _, _, found = cache.Get("MATCH\n(n)\nRETURN\nn") if !found { t.Error("Query with newlines should hit cache") } } func TestQueryPlanCache_LRUEviction(t *testing.T) { cache := NewQueryPlanCache(3) // Small cache clauses := []Clause{&MatchClause{}} // Fill cache cache.Put("query1", clauses, QueryMatch) cache.Put("query2", clauses, QueryMatch) cache.Put("query3", clauses, QueryMatch) // Access query1 to make it recently used cache.Get("query1") // Add fourth item - should evict query2 (least recently used) cache.Put("query4", clauses, QueryMatch) // query2 should be evicted _, _, found := cache.Get("query2") if found { t.Error("query2 should have been evicted") } // query1 should still be there _, _, found = cache.Get("query1") if !found { t.Error("query1 should still be cached") } } func TestQueryPlanCache_Stats(t *testing.T) { cache := NewQueryPlanCache(10) clauses := []Clause{&MatchClause{}} // Initial stats hits, misses, size := cache.Stats() if hits != 0 || misses != 0 || size != 0 { t.Errorf("Initial stats wrong: hits=%d, misses=%d, size=%d", hits, misses, size) } // Cache miss cache.Get("query1") hits, misses, size = cache.Stats() if misses != 1 { t.Errorf("Expected 1 miss, got %d", misses) } // Add and hit cache.Put("query1", clauses, QueryMatch) cache.Get("query1") hits, misses, size = cache.Stats() if hits != 1 || size != 1 { t.Errorf("Expected 1 hit and size 1, got hits=%d, size=%d", hits, size) } } func TestQueryPlanCache_Clear(t *testing.T) { cache := NewQueryPlanCache(10) clauses := []Clause{&MatchClause{}} // Add some entries cache.Put("query1", clauses, QueryMatch) cache.Put("query2", clauses, QueryCreate) cache.Put("query3", clauses, QueryDelete) // Verify they're cached _, _, size := cache.Stats() if size != 3 { t.Errorf("Expected size 3, got %d", size) } // Clear cache.Clear() // All should be gone _, _, size = cache.Stats() if size != 0 { t.Errorf("Expected size 0 after clear, got %d", size) } _, _, found := cache.Get("query1") if found { t.Error("query1 should be cleared") } } func TestQueryPlanCache_Concurrent(t *testing.T) { cache := NewQueryPlanCache(100) clauses := []Clause{&MatchClause{}} var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(id int) { defer wg.Done() for j := 0; j < 100; j++ { key := "query" + string(rune('A'+id)) cache.Put(key, clauses, QueryMatch) cache.Get(key) } }(i) } wg.Wait() // Should not panic or deadlock hits, misses, size := cache.Stats() t.Logf("Concurrent test: hits=%d, misses=%d, size=%d", hits, misses, size) } func TestNormalizeQuery(t *testing.T) { tests := []struct { input string expected string }{ {"MATCH (n) RETURN n", "MATCH (n) RETURN n"}, {"MATCH (n) RETURN n", "MATCH (n) RETURN n"}, {"MATCH\n(n)\nRETURN\nn", "MATCH (n) RETURN n"}, {" MATCH (n) RETURN n ", "MATCH (n) RETURN n"}, {"MATCH\t(n)\t\tRETURN n", "MATCH (n) RETURN n"}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { result := normalizeQuery(tt.input) if result != tt.expected { t.Errorf("normalizeQuery(%q) = %q, expected %q", tt.input, result, tt.expected) } }) } } // ============================================================================= // BENCHMARKS // ============================================================================= func BenchmarkQueryCache_Hit(b *testing.B) { cache := NewQueryCache(1000) result := &ExecuteResult{Columns: []string{"n"}, Rows: [][]interface{}{{"value"}}} cache.Put("query", nil, result, 1*time.Minute) b.ResetTimer() for i := 0; i < b.N; i++ { cache.Get("query", nil) } } func BenchmarkQueryCache_Miss(b *testing.B) { cache := NewQueryCache(1000) b.ResetTimer() for i := 0; i < b.N; i++ { cache.Get("nonexistent", nil) } } func BenchmarkSmartQueryCache_Hit(b *testing.B) { cache := NewSmartQueryCache(1000) result := &ExecuteResult{Columns: []string{"n"}, Rows: [][]interface{}{{"value"}}} cache.PutWithLabels("MATCH (n:User) RETURN n", nil, result, 1*time.Minute, []string{"User"}) b.ResetTimer() for i := 0; i < b.N; i++ { cache.Get("MATCH (n:User) RETURN n", nil) } } func BenchmarkSmartQueryCache_SmartInvalidation(b *testing.B) { cache := NewSmartQueryCache(1000) result := &ExecuteResult{Columns: []string{"n"}} // Pre-populate with different labels for i := 0; i < 100; i++ { cache.PutWithLabels("query"+string(rune(i)), nil, result, 1*time.Minute, []string{"User"}) } for i := 100; i < 200; i++ { cache.PutWithLabels("query"+string(rune(i)), nil, result, 1*time.Minute, []string{"Product"}) } b.ResetTimer() for i := 0; i < b.N; i++ { cache.InvalidateLabels([]string{"User"}) // Re-add for next iteration for j := 0; j < 100; j++ { cache.PutWithLabels("query"+string(rune(j)), nil, result, 1*time.Minute, []string{"User"}) } } } func BenchmarkQueryPlanCache_Hit(b *testing.B) { cache := NewQueryPlanCache(1000) clauses := []Clause{&MatchClause{}} cache.Put("MATCH (n:User) RETURN n", clauses, QueryMatch) b.ResetTimer() for i := 0; i < b.N; i++ { cache.Get("MATCH (n:User) RETURN n") } } func BenchmarkExtractLabelsFromQuery(b *testing.B) { query := "MATCH (u:User)-[:FOLLOWS]->(p:Post)-[:HAS_TAG]->(t:Tag) WHERE u.name = 'Alice' RETURN p" b.ResetTimer() for i := 0; i < b.N; i++ { extractLabelsFromQuery(query) } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server