@@ -571,7 +571,6 @@ describe("Lang.InvertedIndexMap", () => {
571571
572572 // Query people with age 30
573573 const result = peopleIndex . query ( { age : 30 } ) ;
574- console . log ( result ) ;
575574 expect ( result . sort ( ( a , b ) => a . id . localeCompare ( b . id ) ) ) . toEqual (
576575 [ alice , charlie ] . sort ( ( a , b ) => a . id . localeCompare ( b . id ) ) ,
577576 ) ;
@@ -773,4 +772,203 @@ describe("Lang.InvertedIndexMap", () => {
773772 flexIndex . add ( updated ) ;
774773 expect ( flexIndex . query ( { value : 123 } ) ) . toEqual ( [ updated ] ) ;
775774 } ) ;
775+
776+ test ( "queryIndexSet behavior with filtered fields" , ( ) => {
777+ interface Product {
778+ id : string ;
779+ category : string ;
780+ price : number ;
781+ stock : number ;
782+ }
783+
784+ // Only index category and price fields
785+ const idx = new Lang . InvertedIndexMap < Product > (
786+ ( r ) => r . id ,
787+ new Set ( [ "category" , "price" ] ) ,
788+ ) ;
789+
790+ const products = [
791+ { id : "p1" , category : "electronics" , price : 100 , stock : 5 } ,
792+ { id : "p2" , category : "books" , price : 20 , stock : 10 } ,
793+ { id : "p3" , category : "electronics" , price : 200 , stock : 3 } ,
794+ { id : "p4" , category : "books" , price : 15 , stock : 8 } ,
795+ ] ;
796+
797+ products . forEach ( ( p ) => idx . add ( p ) ) ;
798+
799+ // Test querying indexed fields
800+ const electronicsSet = idx . queryIndexSet ( { category : "electronics" } ) ;
801+ expect ( electronicsSet . size ) . toBe ( 2 ) ;
802+ expect ( [ ...electronicsSet ] . map ( ( i ) => products [ i ] . id ) . sort ( ) ) . toEqual (
803+ [ "p1" , "p3" ] . sort ( ) ,
804+ ) ;
805+
806+ // Test querying unindexed fields (should return empty set)
807+ const stockSet = idx . queryIndexSet ( { stock : 5 } ) ;
808+ expect ( stockSet . size ) . toBe ( 4 ) ;
809+
810+ // Test querying combination of indexed and unindexed fields (unindexed fields are ignored)
811+ const mixedSet = idx . queryIndexSet ( { category : "books" , stock : 10 } ) ;
812+ expect ( mixedSet . size ) . toBe ( 2 ) ; // Should match p2 and p4 as they are in "books" category
813+
814+ // Test querying empty object returns set of all indices
815+ const allSet = idx . queryIndexSet ( { } ) ;
816+ expect ( allSet . size ) . toBe ( 4 ) ;
817+
818+ // Test querying non-existent values
819+ const emptySet = idx . queryIndexSet ( { category : "nonexistent" } ) ;
820+ expect ( emptySet . size ) . toBe ( 0 ) ;
821+
822+ // Test querying multiple indexed fields
823+ const booksUnder20Set = idx . queryIndexSet ( {
824+ category : "books" ,
825+ price : 15 ,
826+ } ) ;
827+ expect ( [ ...booksUnder20Set ] . map ( ( i ) => products [ i ] . id ) ) . toEqual ( [ "p4" ] ) ;
828+ } ) ;
829+
830+ test ( "queryIndexSet handles updates correctly" , ( ) => {
831+ interface User {
832+ id : string ;
833+ role : string ;
834+ active : boolean ;
835+ }
836+
837+ const idx = new Lang . InvertedIndexMap < User > ( ( r ) => r . id ) ;
838+
839+ // Add initial users
840+ const users = [
841+ { id : "u1" , role : "admin" , active : true } ,
842+ { id : "u2" , role : "user" , active : true } ,
843+ { id : "u3" , role : "user" , active : false } ,
844+ ] ;
845+
846+ users . forEach ( ( u ) => idx . add ( u ) ) ;
847+
848+ // Initial queries
849+ let activeUsers = idx . queryIndexSet ( { active : true } ) ;
850+ expect ( activeUsers . size ) . toBe ( 2 ) ;
851+
852+ // Update a user
853+ idx . add ( { id : "u2" , role : "admin" , active : false } ) ;
854+
855+ // Check updated queries
856+ activeUsers = idx . queryIndexSet ( { active : true } ) ;
857+ expect ( activeUsers . size ) . toBe ( 1 ) ;
858+
859+ const adminUsers = idx . queryIndexSet ( { role : "admin" } ) ;
860+ expect ( adminUsers . size ) . toBe ( 2 ) ;
861+
862+ // Test combined queries after update
863+ const activeAdmins = idx . queryIndexSet ( { role : "admin" , active : true } ) ;
864+ expect ( activeAdmins . size ) . toBe ( 1 ) ;
865+ } ) ;
866+
867+ test ( "queryIndexSet performance with large datasets" , ( ) => {
868+ interface Record {
869+ id : string ;
870+ type : string ;
871+ value : number ;
872+ }
873+
874+ const idx = new Lang . InvertedIndexMap < Record > ( ( r ) => r . id ) ;
875+ const types = [ "A" , "B" , "C" , "D" ] ;
876+ const values = [ 10 , 20 , 30 , 40 , 50 ] ;
877+
878+ // Add 1000 records
879+ for ( let i = 0 ; i < 1000 ; i ++ ) {
880+ idx . add ( {
881+ id : `r${ i } ` ,
882+ type : types [ i % types . length ] ,
883+ value : values [ i % values . length ] ,
884+ } ) ;
885+ }
886+
887+ // Measure time for single field query
888+ const start1 = performance . now ( ) ;
889+ const typeASet = idx . queryIndexSet ( { type : "A" } ) ;
890+ const duration1 = performance . now ( ) - start1 ;
891+ expect ( typeASet . size ) . toBe ( 250 ) ; // 1000/4 records
892+ expect ( duration1 ) . toBeLessThan ( 50 ) ; // Should be fast
893+
894+ // Measure time for multiple field query
895+ const start2 = performance . now ( ) ;
896+ const typeAValue10Set = idx . queryIndexSet ( { type : "A" , value : 10 } ) ;
897+ const duration2 = performance . now ( ) - start2 ;
898+ expect ( typeAValue10Set . size ) . toBe ( 50 ) ; // 250/5 records
899+ expect ( duration2 ) . toBeLessThan ( 50 ) ; // Should still be fast
900+ } ) ;
901+
902+ test ( "query respects fieldsToIdx constructor parameter" , ( ) => {
903+ interface Product {
904+ id : string ;
905+ name : string ;
906+ category : string ;
907+ price : number ;
908+ inStock : boolean ;
909+ }
910+
911+ const productsIndex = new Lang . InvertedIndexMap < Product > (
912+ ( r ) => r . id ,
913+ new Set ( [ "category" , "price" ] ) , // Only index category and price
914+ ) ;
915+
916+ const products = [
917+ {
918+ id : "p1" ,
919+ name : "Laptop" ,
920+ category : "electronics" ,
921+ price : 999 ,
922+ inStock : true ,
923+ } ,
924+ {
925+ id : "p2" ,
926+ name : "Phone" ,
927+ category : "electronics" ,
928+ price : 599 ,
929+ inStock : false ,
930+ } ,
931+ { id : "p3" , name : "Book" , category : "books" , price : 29 , inStock : true } ,
932+ {
933+ id : "p4" ,
934+ name : "Tablet" ,
935+ category : "electronics" ,
936+ price : 399 ,
937+ inStock : true ,
938+ } ,
939+ ] ;
940+
941+ products . forEach ( ( p ) => productsIndex . add ( p ) ) ;
942+
943+ // Should work for indexed fields
944+ expect ( productsIndex . query ( { category : "electronics" } ) . length ) . toBe ( 3 ) ;
945+ expect ( productsIndex . query ( { price : 599 } ) . length ) . toBe ( 1 ) ;
946+ expect (
947+ productsIndex . query ( { category : "electronics" , price : 999 } ) . length ,
948+ ) . toBe ( 1 ) ;
949+
950+ // Should ignore non-indexed fields
951+ expect ( productsIndex . query ( { name : "Laptop" } ) . length ) . toBe ( 4 ) ;
952+ expect ( productsIndex . query ( { inStock : true } ) . length ) . toBe ( 4 ) ;
953+
954+ // Should ignore non-indexed fields when combined with indexed fields
955+ expect (
956+ productsIndex . query ( { category : "electronics" , name : "Laptop" } ) . length ,
957+ ) . toBe ( 3 ) ;
958+ expect ( productsIndex . query ( { price : 599 , inStock : false } ) . length ) . toBe ( 1 ) ;
959+
960+ // Should handle updates correctly for indexed fields
961+ productsIndex . add ( {
962+ id : "p2" ,
963+ name : "Phone Updated" ,
964+ category : "accessories" ,
965+ price : 499 ,
966+ inStock : true ,
967+ } ) ;
968+
969+ expect ( productsIndex . query ( { category : "electronics" } ) . length ) . toBe ( 2 ) ;
970+ expect ( productsIndex . query ( { category : "accessories" } ) . length ) . toBe ( 1 ) ;
971+ expect ( productsIndex . query ( { price : 599 } ) . length ) . toBe ( 0 ) ;
972+ expect ( productsIndex . query ( { price : 499 } ) . length ) . toBe ( 1 ) ;
973+ } ) ;
776974} ) ;
0 commit comments