Skip to main content
Glama
orneryd

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

by orneryd
unwind_labels_test.go12.2 kB
// Unit tests for UNWIND with labels() and list comprehension. // These queries are used by Mimir's index-stats API and are currently failing. package cypher import ( "context" "testing" "github.com/orneryd/nornicdb/pkg/storage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // ==================================================================================== // Setup helper - creates nodes with multiple labels // ==================================================================================== func setupMultiLabelNodes(t *testing.T, store storage.Engine, exec *StorageExecutor) { ctx := context.Background() queries := []string{ `CREATE (n:File:TypeScript {path: '/test/file1.ts', extension: '.ts'})`, `CREATE (n:File:JavaScript {path: '/test/file2.js', extension: '.js'})`, `CREATE (n:File:TypeScript {path: '/test/file3.ts', extension: '.ts'})`, `CREATE (n:File:Markdown {path: '/test/file4.md', extension: '.md'})`, `CREATE (n:File {path: '/test/file5.txt', extension: '.txt'})`, // No secondary label } for _, q := range queries { _, err := exec.Execute(ctx, q, nil) require.NoError(t, err) } } // ==================================================================================== // labels() function tests // ==================================================================================== func TestLabelsFunction(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() setupMultiLabelNodes(t, store, exec) t.Run("labels() returns all node labels", func(t *testing.T) { result, err := exec.Execute(ctx, ` MATCH (f:File) RETURN f.path, labels(f) ORDER BY f.path `, nil) require.NoError(t, err) require.Len(t, result.Rows, 5) // First row should be file1.ts with [File, TypeScript] // labels() returns []interface{} for Cypher compatibility labels1, ok := result.Rows[0][1].([]interface{}) require.True(t, ok, "labels() should return []interface{}") labelsStr := make([]string, len(labels1)) for i, l := range labels1 { labelsStr[i] = l.(string) } assert.Contains(t, labelsStr, "File") assert.Contains(t, labelsStr, "TypeScript") }) t.Run("labels() on single-label node", func(t *testing.T) { result, err := exec.Execute(ctx, ` MATCH (f:File {path: '/test/file5.txt'}) RETURN labels(f) `, nil) require.NoError(t, err) require.Len(t, result.Rows, 1) labels, ok := result.Rows[0][0].([]interface{}) require.True(t, ok, "labels() should return []interface{}") assert.Len(t, labels, 1) assert.Equal(t, "File", labels[0]) }) } // ==================================================================================== // UNWIND with function results // ==================================================================================== func TestUnwindWithFunctionResults(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() setupMultiLabelNodes(t, store, exec) t.Run("UNWIND labels(f) expands node labels", func(t *testing.T) { result, err := exec.Execute(ctx, ` MATCH (f:File {path: '/test/file1.ts'}) UNWIND labels(f) as label RETURN label `, nil) require.NoError(t, err) // Should return 2 rows: File, TypeScript require.Len(t, result.Rows, 2, "Should have 2 rows from UNWIND labels()") if len(result.Rows) >= 2 { labels := []string{} for _, row := range result.Rows { if s, ok := row[0].(string); ok { labels = append(labels, s) } } assert.Contains(t, labels, "File") assert.Contains(t, labels, "TypeScript") } }) t.Run("UNWIND range() function", func(t *testing.T) { result, err := exec.Execute(ctx, ` UNWIND range(1, 5) AS x RETURN x `, nil) require.NoError(t, err) require.Len(t, result.Rows, 5) }) t.Run("UNWIND keys() function", func(t *testing.T) { result, err := exec.Execute(ctx, ` MATCH (f:File {path: '/test/file1.ts'}) UNWIND keys(f) as key RETURN key `, nil) require.NoError(t, err) // Should have at least 'path' and 'extension' require.GreaterOrEqual(t, len(result.Rows), 2) }) } // ==================================================================================== // List comprehension tests // ==================================================================================== func TestListComprehensionWithLabels(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() setupMultiLabelNodes(t, store, exec) t.Run("basic list comprehension [x IN list]", func(t *testing.T) { result, err := exec.Execute(ctx, ` WITH [1, 2, 3, 4, 5] as numbers RETURN [x IN numbers] as result `, nil) require.NoError(t, err) require.Len(t, result.Rows, 1) resultList, ok := result.Rows[0][0].([]interface{}) require.True(t, ok) assert.Len(t, resultList, 5) }) t.Run("list comprehension with WHERE filter", func(t *testing.T) { result, err := exec.Execute(ctx, ` WITH [1, 2, 3, 4, 5] as numbers RETURN [x IN numbers WHERE x > 2] as filtered `, nil) require.NoError(t, err) require.Len(t, result.Rows, 1) resultList, ok := result.Rows[0][0].([]interface{}) require.True(t, ok) assert.Len(t, resultList, 3) // 3, 4, 5 }) t.Run("list comprehension with transformation", func(t *testing.T) { result, err := exec.Execute(ctx, ` WITH [1, 2, 3] as numbers RETURN [x IN numbers | x * 2] as doubled `, nil) require.NoError(t, err) require.Len(t, result.Rows, 1) resultList, ok := result.Rows[0][0].([]interface{}) require.True(t, ok) // Should be [2, 4, 6] assert.Len(t, resultList, 3) }) t.Run("list comprehension on labels() with WHERE", func(t *testing.T) { // This is the exact pattern from Mimir's byType query result, err := exec.Execute(ctx, ` MATCH (f:File {path: '/test/file1.ts'}) WITH f, [label IN labels(f) WHERE label <> 'File'] as filteredLabels RETURN filteredLabels `, nil) require.NoError(t, err) require.Len(t, result.Rows, 1) filteredLabels, ok := result.Rows[0][0].([]interface{}) require.True(t, ok, "Should return a list") assert.Len(t, filteredLabels, 1) assert.Equal(t, "TypeScript", filteredLabels[0]) }) t.Run("list comprehension filtering strings", func(t *testing.T) { result, err := exec.Execute(ctx, ` WITH ['File', 'TypeScript', 'Node'] as labels RETURN [label IN labels WHERE label <> 'File'] as filtered `, nil) require.NoError(t, err) require.Len(t, result.Rows, 1) filtered, ok := result.Rows[0][0].([]interface{}) require.True(t, ok) assert.Len(t, filtered, 2) }) } // ==================================================================================== // MATCH ... UNWIND combined queries // ==================================================================================== func TestMatchUnwindCombined(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() setupMultiLabelNodes(t, store, exec) t.Run("MATCH then UNWIND labels", func(t *testing.T) { result, err := exec.Execute(ctx, ` MATCH (f:File) UNWIND labels(f) as label WHERE label <> 'File' RETURN label, count(*) as count `, nil) require.NoError(t, err) // Should have TypeScript(2), JavaScript(1), Markdown(1) require.GreaterOrEqual(t, len(result.Rows), 3) }) t.Run("MATCH with WITH then UNWIND", func(t *testing.T) { result, err := exec.Execute(ctx, ` MATCH (f:File) WITH f, labels(f) as nodeLabels UNWIND nodeLabels as label WHERE label <> 'File' RETURN label, count(*) as count ORDER BY count DESC `, nil) require.NoError(t, err) // TypeScript should have count 2, others count 1 require.GreaterOrEqual(t, len(result.Rows), 1) }) } // ==================================================================================== // The exact Mimir byType query // ==================================================================================== func TestMimirByTypeQuery(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() setupMultiLabelNodes(t, store, exec) t.Run("exact Mimir byType query", func(t *testing.T) { // This is the exact query from Mimir's index-api.ts line 682-689 result, err := exec.Execute(ctx, ` MATCH (f:File) WITH f, [label IN labels(f) WHERE label <> 'File'] as filteredLabels UNWIND filteredLabels as label WITH label, COUNT(f) as count RETURN label as type, count ORDER BY count DESC `, nil) require.NoError(t, err) // Should return TypeScript(2), JavaScript(1), Markdown(1) // File5.txt has no secondary label so doesn't appear require.Len(t, result.Rows, 3, "Should have 3 type groups") // First should be TypeScript with count 2 assert.Equal(t, "TypeScript", result.Rows[0][0]) assert.Equal(t, int64(2), result.Rows[0][1]) }) t.Run("files with no secondary label are excluded", func(t *testing.T) { // File5.txt only has label 'File', so after filtering it has empty list // UNWIND of empty list produces no rows result, err := exec.Execute(ctx, ` MATCH (f:File {path: '/test/file5.txt'}) WITH f, [label IN labels(f) WHERE label <> 'File'] as filteredLabels UNWIND filteredLabels as label RETURN label `, nil) require.NoError(t, err) assert.Len(t, result.Rows, 0, "UNWIND of empty list should produce no rows") }) } // ==================================================================================== // UNWIND edge cases // ==================================================================================== func TestUnwindEdgeCases(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() t.Run("UNWIND empty list", func(t *testing.T) { result, err := exec.Execute(ctx, ` UNWIND [] AS x RETURN x `, nil) require.NoError(t, err) assert.Len(t, result.Rows, 0) }) t.Run("UNWIND null produces no rows", func(t *testing.T) { result, err := exec.Execute(ctx, ` UNWIND null AS x RETURN x `, nil) require.NoError(t, err) assert.Len(t, result.Rows, 0) }) t.Run("UNWIND nested list", func(t *testing.T) { result, err := exec.Execute(ctx, ` UNWIND [[1, 2], [3, 4]] AS x RETURN x `, nil) require.NoError(t, err) assert.Len(t, result.Rows, 2) }) t.Run("UNWIND with aggregation", func(t *testing.T) { result, err := exec.Execute(ctx, ` UNWIND [1, 2, 3, 4, 5] AS x RETURN sum(x) as total `, nil) require.NoError(t, err) require.Len(t, result.Rows, 1) // 1+2+3+4+5 = 15 if total, ok := result.Rows[0][0].(float64); ok { assert.Equal(t, float64(15), total) } else if total, ok := result.Rows[0][0].(int64); ok { assert.Equal(t, int64(15), total) } }) t.Run("UNWIND preserves variables from MATCH", func(t *testing.T) { setupMultiLabelNodes(t, store, exec) result, err := exec.Execute(ctx, ` MATCH (f:File {path: '/test/file1.ts'}) UNWIND [1, 2] AS x RETURN f.path, x `, nil) require.NoError(t, err) require.Len(t, result.Rows, 2, "Should have 2 rows from UNWIND") // Both rows should have the same path if len(result.Rows) >= 2 { assert.Equal(t, "/test/file1.ts", result.Rows[0][0]) assert.Equal(t, "/test/file1.ts", result.Rows[1][0]) } }) } // ==================================================================================== // Alternative simpler query for byType (workaround) // ==================================================================================== func TestSimplifiedByTypeQuery(t *testing.T) { store := storage.NewMemoryEngine() exec := NewStorageExecutor(store) ctx := context.Background() setupMultiLabelNodes(t, store, exec) t.Run("simplified byType using direct label check", func(t *testing.T) { // Alternative approach: query each known type separately // This is a workaround if list comprehension isn't supported // Count TypeScript files result, err := exec.Execute(ctx, ` MATCH (f:TypeScript) RETURN 'TypeScript' as type, count(*) as count `, nil) require.NoError(t, err) require.Len(t, result.Rows, 1) assert.Equal(t, int64(2), result.Rows[0][1]) }) }

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