Skip to main content
Glama
orneryd

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

by orneryd
explain_test.go13 kB
package cypher import ( "context" "strings" "testing" "github.com/orneryd/nornicdb/pkg/storage" "github.com/stretchr/testify/require" ) func TestParseExecutionMode(t *testing.T) { tests := []struct { name string query string expectedMode ExecutionMode expectedQuery string }{ { name: "normal query", query: "MATCH (n) RETURN n", expectedMode: ModeNormal, expectedQuery: "MATCH (n) RETURN n", }, { name: "EXPLAIN query", query: "EXPLAIN MATCH (n) RETURN n", expectedMode: ModeExplain, expectedQuery: "MATCH (n) RETURN n", }, { name: "PROFILE query", query: "PROFILE MATCH (n) RETURN n", expectedMode: ModeProfile, expectedQuery: "MATCH (n) RETURN n", }, { name: "lowercase explain", query: "explain MATCH (n) RETURN n", expectedMode: ModeExplain, expectedQuery: "MATCH (n) RETURN n", }, { name: "lowercase profile", query: "profile MATCH (n) RETURN n", expectedMode: ModeProfile, expectedQuery: "MATCH (n) RETURN n", }, { name: "explain with whitespace", query: " EXPLAIN MATCH (n) RETURN n ", expectedMode: ModeExplain, expectedQuery: "MATCH (n) RETURN n", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mode, query := parseExecutionMode(tt.query) require.Equal(t, tt.expectedMode, mode) require.Equal(t, tt.expectedQuery, query) }) } } func TestExplainBasicQuery(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() // Create some test data exec.Execute(ctx, `CREATE (n:Person {name: 'Alice', age: 30})`, nil) exec.Execute(ctx, `CREATE (n:Person {name: 'Bob', age: 25})`, nil) t.Run("EXPLAIN MATCH query", func(t *testing.T) { result, err := exec.Execute(ctx, `EXPLAIN MATCH (n:Person) RETURN n`, nil) require.NoError(t, err) require.NotNil(t, result) require.Equal(t, []string{"Plan"}, result.Columns) require.Len(t, result.Rows, 1) // Check that plan is returned planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "EXPLAIN") require.Contains(t, planStr, "Query Plan") // Check metadata require.NotNil(t, result.Metadata) require.Equal(t, "EXPLAIN", result.Metadata["planType"]) }) t.Run("EXPLAIN with WHERE", func(t *testing.T) { result, err := exec.Execute(ctx, `EXPLAIN MATCH (n:Person) WHERE n.age > 20 RETURN n`, nil) require.NoError(t, err) require.NotNil(t, result) planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "Filter") }) t.Run("EXPLAIN with ORDER BY", func(t *testing.T) { result, err := exec.Execute(ctx, `EXPLAIN MATCH (n:Person) RETURN n ORDER BY n.age`, nil) require.NoError(t, err) require.NotNil(t, result) planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "Sort") }) t.Run("EXPLAIN with LIMIT", func(t *testing.T) { result, err := exec.Execute(ctx, `EXPLAIN MATCH (n:Person) RETURN n LIMIT 10`, nil) require.NoError(t, err) require.NotNil(t, result) planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "Limit") }) t.Run("EXPLAIN with aggregation", func(t *testing.T) { result, err := exec.Execute(ctx, `EXPLAIN MATCH (n:Person) RETURN count(n)`, nil) require.NoError(t, err) require.NotNil(t, result) planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "EagerAggregation") }) t.Run("EXPLAIN CALL procedure", func(t *testing.T) { result, err := exec.Execute(ctx, `EXPLAIN CALL db.labels()`, nil) require.NoError(t, err) require.NotNil(t, result) planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "ProcedureCall") require.Contains(t, planStr, "db.labels") }) } func TestProfileBasicQuery(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() // Create some test data exec.Execute(ctx, `CREATE (n:Person {name: 'Alice', age: 30})`, nil) exec.Execute(ctx, `CREATE (n:Person {name: 'Bob', age: 25})`, nil) exec.Execute(ctx, `CREATE (n:Person {name: 'Carol', age: 35})`, nil) t.Run("PROFILE MATCH query", func(t *testing.T) { result, err := exec.Execute(ctx, `PROFILE MATCH (n:Person) RETURN n`, nil) require.NoError(t, err) require.NotNil(t, result) require.Equal(t, []string{"Plan"}, result.Columns) require.Len(t, result.Rows, 1) // Check that plan is returned with statistics planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "PROFILE") require.Contains(t, planStr, "Query Plan") require.Contains(t, planStr, "Total Time:") require.Contains(t, planStr, "Total Rows:") require.Contains(t, planStr, "Total DB Hits:") // Check metadata require.NotNil(t, result.Metadata) require.Equal(t, "PROFILE", result.Metadata["planType"]) // Check plan object in metadata plan := result.Metadata["plan"].(*ExecutionPlan) require.NotNil(t, plan) require.Equal(t, ModeProfile, plan.Mode) require.Greater(t, plan.TotalRows, int64(0)) }) t.Run("PROFILE with WHERE filters results", func(t *testing.T) { result, err := exec.Execute(ctx, `PROFILE MATCH (n:Person) WHERE n.age > 28 RETURN n`, nil) require.NoError(t, err) require.NotNil(t, result) plan := result.Metadata["plan"].(*ExecutionPlan) // Should only return Alice (30) and Carol (35) require.Equal(t, int64(2), plan.TotalRows) }) t.Run("PROFILE with LIMIT", func(t *testing.T) { result, err := exec.Execute(ctx, `PROFILE MATCH (n:Person) RETURN n LIMIT 1`, nil) require.NoError(t, err) require.NotNil(t, result) plan := result.Metadata["plan"].(*ExecutionPlan) require.Equal(t, int64(1), plan.TotalRows) }) t.Run("PROFILE with aggregation", func(t *testing.T) { result, err := exec.Execute(ctx, `PROFILE MATCH (n:Person) RETURN count(n) AS cnt`, nil) require.NoError(t, err) require.NotNil(t, result) plan := result.Metadata["plan"].(*ExecutionPlan) require.Equal(t, int64(1), plan.TotalRows) // One aggregation result }) } func TestExplainComplexQueries(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() // Create test graph exec.Execute(ctx, `CREATE (a:Person {name: 'Alice'})`, nil) exec.Execute(ctx, `CREATE (b:Person {name: 'Bob'})`, nil) exec.Execute(ctx, `MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'}) CREATE (a)-[:KNOWS]->(b)`, nil) t.Run("EXPLAIN relationship query", func(t *testing.T) { result, err := exec.Execute(ctx, `EXPLAIN MATCH (a:Person)-[:KNOWS]->(b:Person) RETURN a, b`, nil) require.NoError(t, err) require.NotNil(t, result) planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "Expand") }) t.Run("EXPLAIN shortestPath", func(t *testing.T) { result, err := exec.Execute(ctx, `EXPLAIN MATCH p = shortestPath((a:Person)-[*]-(b:Person)) RETURN p`, nil) require.NoError(t, err) require.NotNil(t, result) planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "ShortestPath") }) t.Run("EXPLAIN CREATE", func(t *testing.T) { result, err := exec.Execute(ctx, `EXPLAIN CREATE (n:Test {name: 'test'})`, nil) require.NoError(t, err) require.NotNil(t, result) planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "CreateNode") }) t.Run("EXPLAIN MERGE", func(t *testing.T) { result, err := exec.Execute(ctx, `EXPLAIN MERGE (n:Test {name: 'test'})`, nil) require.NoError(t, err) require.NotNil(t, result) planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "Merge") }) } func TestExplainPlanStructure(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() t.Run("Plan has correct operator types", func(t *testing.T) { result, err := exec.Execute(ctx, `EXPLAIN MATCH (n:Person) WHERE n.age > 25 RETURN n ORDER BY n.name LIMIT 10`, nil) require.NoError(t, err) require.NotNil(t, result) plan := result.Metadata["plan"].(*ExecutionPlan) require.NotNil(t, plan.Root) // Collect all operator types ops := collectOperatorTypes(plan.Root) // Should contain expected operators // Note: NodeByLabelScan is used because the WHERE is handled by Filter operator require.Contains(t, ops, "NodeByLabelScan") require.Contains(t, ops, "Filter") require.Contains(t, ops, "Sort") require.Contains(t, ops, "Limit") require.Contains(t, ops, "ProduceResults") }) t.Run("Plan shows estimated rows", func(t *testing.T) { result, err := exec.Execute(ctx, `EXPLAIN MATCH (n:Person) RETURN n`, nil) require.NoError(t, err) planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "Estimated Rows:") }) } func TestProfileDBHits(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() // Create some data for i := 0; i < 10; i++ { exec.Execute(ctx, `CREATE (n:Node {id: $id})`, map[string]interface{}{"id": i}) } t.Run("Profile shows DB hits", func(t *testing.T) { result, err := exec.Execute(ctx, `PROFILE MATCH (n:Node) RETURN n`, nil) require.NoError(t, err) plan := result.Metadata["plan"].(*ExecutionPlan) require.Greater(t, plan.TotalDBHits, int64(0)) }) } func TestExplainNoExecution(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() t.Run("EXPLAIN does not execute CREATE", func(t *testing.T) { // Count nodes before before, _ := store.AllNodes() beforeCount := len(before) // EXPLAIN CREATE _, err := exec.Execute(ctx, `EXPLAIN CREATE (n:TestNode {name: 'should_not_exist'})`, nil) require.NoError(t, err) // Count nodes after - should be same after, _ := store.AllNodes() afterCount := len(after) require.Equal(t, beforeCount, afterCount, "EXPLAIN should not create nodes") }) t.Run("EXPLAIN does not execute DELETE", func(t *testing.T) { // Create a node exec.Execute(ctx, `CREATE (n:ToDelete {name: 'test'})`, nil) before, _ := store.AllNodes() beforeCount := len(before) // EXPLAIN DELETE _, err := exec.Execute(ctx, `EXPLAIN MATCH (n:ToDelete) DELETE n`, nil) require.NoError(t, err) // Node should still exist after, _ := store.AllNodes() afterCount := len(after) require.Equal(t, beforeCount, afterCount, "EXPLAIN should not delete nodes") }) } // Helper to collect all operator types from a plan func collectOperatorTypes(op *PlanOperator) []string { if op == nil { return nil } types := []string{op.OperatorType} for _, child := range op.Children { types = append(types, collectOperatorTypes(child)...) } return types } func TestExplainVsProfileOutput(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() // Create data exec.Execute(ctx, `CREATE (n:Person {name: 'Alice'})`, nil) t.Run("EXPLAIN shows estimated rows only", func(t *testing.T) { result, err := exec.Execute(ctx, `EXPLAIN MATCH (n:Person) RETURN n`, nil) require.NoError(t, err) planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "Estimated Rows:") require.NotContains(t, planStr, "Actual:") }) t.Run("PROFILE shows both estimated and actual", func(t *testing.T) { result, err := exec.Execute(ctx, `PROFILE MATCH (n:Person) RETURN n`, nil) require.NoError(t, err) planStr := result.Rows[0][0].(string) require.Contains(t, planStr, "Est:") require.Contains(t, planStr, "Actual:") require.Contains(t, planStr, "Hits:") }) } func TestAnalyzeNodeScan(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) tests := []struct { name string query string expectedType string }{ { name: "AllNodesScan for no label", query: "MATCH (n) RETURN n", expectedType: "AllNodesScan", }, { name: "NodeByLabelScan for label only", query: "MATCH (n:Person) RETURN n", expectedType: "NodeByLabelScan", }, { name: "NodeIndexSeek for label with property", query: "MATCH (n:Person {name: 'Alice'}) RETURN n", expectedType: "NodeIndexSeek", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { op := exec.analyzeNodeScan(tt.query) require.Equal(t, tt.expectedType, op.OperatorType) }) } } func TestTruncate(t *testing.T) { tests := []struct { input string maxLen int expected string }{ {"hello", 10, "hello"}, {"hello world", 8, "hello..."}, {"hi", 2, "hi"}, {"", 5, ""}, } for _, tt := range tests { result := truncate(tt.input, tt.maxLen) expectedPrefix := tt.expected if len(expectedPrefix) > 3 { expectedPrefix = expectedPrefix[:3] } if !strings.HasPrefix(result, expectedPrefix) { t.Errorf("truncate(%q, %d) = %q, want prefix of %q", tt.input, tt.maxLen, result, tt.expected) } } }

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