import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as smartmongo from '../ts/index.js'; const { QueryPlanner, IndexEngine, MemoryStorageAdapter, ObjectId } = smartmongo.tsmdb; let storage: InstanceType; let indexEngine: InstanceType; let queryPlanner: InstanceType; const TEST_DB = 'testdb'; const TEST_COLL = 'testcoll'; // ============================================================================ // Setup // ============================================================================ tap.test('queryplanner: should create QueryPlanner instance', async () => { storage = new MemoryStorageAdapter(); await storage.initialize(); await storage.createCollection(TEST_DB, TEST_COLL); indexEngine = new IndexEngine(TEST_DB, TEST_COLL, storage); queryPlanner = new QueryPlanner(indexEngine); expect(queryPlanner).toBeTruthy(); }); tap.test('queryplanner: should insert test documents', async () => { // Insert test documents const docs = [ { _id: new ObjectId(), name: 'Alice', age: 25, city: 'NYC', category: 'A' }, { _id: new ObjectId(), name: 'Bob', age: 30, city: 'LA', category: 'B' }, { _id: new ObjectId(), name: 'Charlie', age: 35, city: 'NYC', category: 'A' }, { _id: new ObjectId(), name: 'Diana', age: 28, city: 'Chicago', category: 'C' }, { _id: new ObjectId(), name: 'Eve', age: 32, city: 'LA', category: 'B' }, ]; for (const doc of docs) { await storage.insertOne(TEST_DB, TEST_COLL, doc); } }); // ============================================================================ // Basic Plan Tests // ============================================================================ tap.test('queryplanner: empty filter should result in COLLSCAN', async () => { const plan = await queryPlanner.plan({}); expect(plan.type).toEqual('COLLSCAN'); expect(plan.indexCovering).toBeFalse(); expect(plan.selectivity).toEqual(1.0); expect(plan.explanation).toContain('No filter'); }); tap.test('queryplanner: null filter should result in COLLSCAN', async () => { const plan = await queryPlanner.plan(null as any); expect(plan.type).toEqual('COLLSCAN'); }); tap.test('queryplanner: filter with no matching index should result in COLLSCAN', async () => { const plan = await queryPlanner.plan({ nonExistentField: 'value' }); expect(plan.type).toEqual('COLLSCAN'); expect(plan.explanation).toContain('No suitable index'); }); // ============================================================================ // Index Scan Tests (with indexes) // ============================================================================ tap.test('queryplanner: should create test indexes', async () => { await indexEngine.createIndex({ age: 1 }, { name: 'age_1' }); await indexEngine.createIndex({ name: 1 }, { name: 'name_1' }); await indexEngine.createIndex({ city: 1, category: 1 }, { name: 'city_category_1' }); const indexes = await indexEngine.listIndexes(); expect(indexes.length).toBeGreaterThanOrEqual(4); // _id_ + 3 created }); tap.test('queryplanner: simple equality filter should use IXSCAN', async () => { const plan = await queryPlanner.plan({ age: 30 }); expect(plan.type).toEqual('IXSCAN'); expect(plan.indexName).toEqual('age_1'); expect(plan.indexFieldsUsed).toContain('age'); expect(plan.usesRange).toBeFalse(); }); tap.test('queryplanner: $eq operator should use IXSCAN', async () => { const plan = await queryPlanner.plan({ name: { $eq: 'Alice' } }); expect(plan.type).toEqual('IXSCAN'); expect(plan.indexName).toEqual('name_1'); expect(plan.indexFieldsUsed).toContain('name'); }); tap.test('queryplanner: range filter ($gt) should use IXSCAN_RANGE', async () => { const plan = await queryPlanner.plan({ age: { $gt: 25 } }); expect(plan.type).toEqual('IXSCAN_RANGE'); expect(plan.indexName).toEqual('age_1'); expect(plan.usesRange).toBeTrue(); }); tap.test('queryplanner: range filter ($lt) should use IXSCAN_RANGE', async () => { const plan = await queryPlanner.plan({ age: { $lt: 35 } }); expect(plan.type).toEqual('IXSCAN_RANGE'); expect(plan.usesRange).toBeTrue(); }); tap.test('queryplanner: range filter ($gte, $lte) should use IXSCAN_RANGE', async () => { const plan = await queryPlanner.plan({ age: { $gte: 25, $lte: 35 } }); expect(plan.type).toEqual('IXSCAN_RANGE'); expect(plan.usesRange).toBeTrue(); }); tap.test('queryplanner: $in operator should use IXSCAN', async () => { const plan = await queryPlanner.plan({ age: { $in: [25, 30, 35] } }); expect(plan.type).toEqual('IXSCAN'); expect(plan.indexName).toEqual('age_1'); }); // ============================================================================ // Compound Index Tests // ============================================================================ tap.test('queryplanner: compound index - first field equality should use index', async () => { const plan = await queryPlanner.plan({ city: 'NYC' }); expect(plan.type).toEqual('IXSCAN'); expect(plan.indexName).toEqual('city_category_1'); expect(plan.indexFieldsUsed).toContain('city'); }); tap.test('queryplanner: compound index - both fields should use full index', async () => { const plan = await queryPlanner.plan({ city: 'NYC', category: 'A' }); expect(plan.type).toEqual('IXSCAN'); expect(plan.indexName).toEqual('city_category_1'); expect(plan.indexFieldsUsed).toContain('city'); expect(plan.indexFieldsUsed).toContain('category'); expect(plan.indexFieldsUsed.length).toEqual(2); }); // ============================================================================ // Selectivity Tests // ============================================================================ tap.test('queryplanner: equality query should have low selectivity', async () => { const plan = await queryPlanner.plan({ age: 30 }); expect(plan.selectivity).toBeLessThan(0.1); }); tap.test('queryplanner: range query should have moderate selectivity', async () => { const plan = await queryPlanner.plan({ age: { $gt: 25 } }); expect(plan.selectivity).toBeGreaterThan(0); expect(plan.selectivity).toBeLessThan(1); }); tap.test('queryplanner: $in query selectivity depends on array size', async () => { const smallInPlan = await queryPlanner.plan({ age: { $in: [25] } }); const largeInPlan = await queryPlanner.plan({ age: { $in: [25, 26, 27, 28, 29, 30] } }); // Larger $in should have higher selectivity (less selective = more documents) expect(largeInPlan.selectivity).toBeGreaterThanOrEqual(smallInPlan.selectivity); }); // ============================================================================ // Index Covering Tests // ============================================================================ tap.test('queryplanner: query covering all filter fields should be index covering', async () => { const plan = await queryPlanner.plan({ age: 30 }); // All filter fields are covered by the index expect(plan.indexCovering).toBeTrue(); }); tap.test('queryplanner: query with residual filter should not be index covering', async () => { const plan = await queryPlanner.plan({ city: 'NYC', name: 'Alice' }); // 'name' is not in the compound index city_category, so it's residual expect(plan.indexCovering).toBeFalse(); expect(plan.residualFilter).toBeTruthy(); }); // ============================================================================ // Explain Tests // ============================================================================ tap.test('queryplanner: explain should return detailed plan info', async () => { const explanation = await queryPlanner.explain({ age: 30 }); expect(explanation.queryPlanner).toBeTruthy(); expect(explanation.queryPlanner.plannerVersion).toEqual(1); expect(explanation.queryPlanner.winningPlan).toBeTruthy(); expect(explanation.queryPlanner.rejectedPlans).toBeArray(); }); tap.test('queryplanner: explain should include winning and rejected plans', async () => { const explanation = await queryPlanner.explain({ age: 30 }); expect(explanation.queryPlanner.winningPlan.type).toBeTruthy(); expect(explanation.queryPlanner.rejectedPlans.length).toBeGreaterThan(0); }); tap.test('queryplanner: explain winning plan should be the best plan', async () => { const explanation = await queryPlanner.explain({ age: 30 }); // Winning plan should use an index, not collection scan (if index exists) expect(explanation.queryPlanner.winningPlan.type).toEqual('IXSCAN'); // There should be a COLLSCAN in rejected plans const hasCOLLSCAN = explanation.queryPlanner.rejectedPlans.some(p => p.type === 'COLLSCAN'); expect(hasCOLLSCAN).toBeTrue(); }); // ============================================================================ // $and Operator Tests // ============================================================================ tap.test('queryplanner: $and conditions should be analyzed', async () => { const plan = await queryPlanner.plan({ $and: [ { age: { $gte: 25 } }, { age: { $lte: 35 } }, ], }); expect(plan.type).toEqual('IXSCAN_RANGE'); expect(plan.indexName).toEqual('age_1'); }); // ============================================================================ // Edge Cases // ============================================================================ tap.test('queryplanner: should handle complex nested operators', async () => { const plan = await queryPlanner.plan({ age: { $gte: 20, $lte: 40 }, city: 'NYC', }); expect(plan).toBeTruthy(); expect(plan.type).not.toBeUndefined(); }); tap.test('queryplanner: should handle $exists operator', async () => { await indexEngine.createIndex({ email: 1 }, { name: 'email_1', sparse: true }); const plan = await queryPlanner.plan({ email: { $exists: true } }); // $exists can use sparse indexes expect(plan).toBeTruthy(); }); // ============================================================================ // Cleanup // ============================================================================ tap.test('queryplanner: cleanup', async () => { await storage.close(); expect(true).toBeTrue(); }); export default tap.start();