@@ -925,4 +925,160 @@ public function testObjectAttributeDefaults(): void
925925 // Clean up
926926 $ database ->deleteCollection ($ collectionId );
927927 }
928+
929+ public function testMetadataWithVector (): void
930+ {
931+ /** @var Database $database */
932+ $ database = static ::getDatabase ();
933+
934+ // Skip if adapter doesn't support either vectors or object attributes
935+ if (!$ database ->getAdapter ()->getSupportForVectors () || !$ database ->getAdapter ()->getSupportForObject ()) {
936+ $ this ->expectNotToPerformAssertions ();
937+ return ;
938+ }
939+
940+ $ collectionId = ID ::unique ();
941+ $ database ->createCollection ($ collectionId );
942+
943+ // Attributes: 3D vector and nested metadata object
944+ $ database ->createAttribute ($ collectionId , 'embedding ' , Database::VAR_VECTOR , 3 , true );
945+ $ database ->createAttribute ($ collectionId , 'metadata ' , Database::VAR_OBJECT , 0 , false );
946+
947+ // Seed documents
948+ $ docA = $ database ->createDocument ($ collectionId , new Document ([
949+ '$id ' => 'vecA ' ,
950+ '$permissions ' => [Permission::read (Role::any ())],
951+ 'embedding ' => [0.1 , 0.2 , 0.9 ],
952+ 'metadata ' => [
953+ 'profile ' => [
954+ 'user ' => [
955+ 'info ' => [
956+ 'country ' => 'IN ' ,
957+ 'score ' => 100
958+ ]
959+ ]
960+ ],
961+ 'tags ' => ['ai ' , 'ml ' , 'db ' ],
962+ 'settings ' => [
963+ 'prefs ' => [
964+ 'theme ' => 'dark ' ,
965+ 'features ' => [
966+ 'experimental ' => true
967+ ]
968+ ]
969+ ]
970+ ]
971+ ]));
972+
973+ $ docB = $ database ->createDocument ($ collectionId , new Document ([
974+ '$id ' => 'vecB ' ,
975+ '$permissions ' => [Permission::read (Role::any ())],
976+ 'embedding ' => [0.2 , 0.9 , 0.1 ],
977+ 'metadata ' => [
978+ 'profile ' => [
979+ 'user ' => [
980+ 'info ' => [
981+ 'country ' => 'US ' ,
982+ 'score ' => 80
983+ ]
984+ ]
985+ ],
986+ 'tags ' => ['search ' , 'analytics ' ],
987+ 'settings ' => [
988+ 'prefs ' => [
989+ 'theme ' => 'light '
990+ ]
991+ ]
992+ ]
993+ ]));
994+
995+ $ docC = $ database ->createDocument ($ collectionId , new Document ([
996+ '$id ' => 'vecC ' ,
997+ '$permissions ' => [Permission::read (Role::any ())],
998+ 'embedding ' => [0.9 , 0.1 , 0.2 ],
999+ 'metadata ' => [
1000+ 'profile ' => [
1001+ 'user ' => [
1002+ 'info ' => [
1003+ 'country ' => 'CA ' ,
1004+ 'score ' => 60
1005+ ]
1006+ ]
1007+ ],
1008+ 'tags ' => ['ml ' , 'cv ' ],
1009+ 'settings ' => [
1010+ 'prefs ' => [
1011+ 'theme ' => 'dark ' ,
1012+ 'features ' => [
1013+ 'experimental ' => false
1014+ ]
1015+ ]
1016+ ]
1017+ ]
1018+ ]));
1019+
1020+ // 1) Vector similarity: closest to [0.0, 0.0, 1.0] should be vecA
1021+ $ results = $ database ->find ($ collectionId , [
1022+ Query::vectorCosine ('embedding ' , [0.0 , 0.0 , 1.0 ]),
1023+ Query::limit (1 )
1024+ ]);
1025+ $ this ->assertCount (1 , $ results );
1026+ $ this ->assertEquals ('vecA ' , $ results [0 ]->getId ());
1027+
1028+ // 2) Complex nested metadata equal (partial object containment)
1029+ $ results = $ database ->find ($ collectionId , [
1030+ Query::equal ('metadata ' , [[
1031+ 'profile ' => [
1032+ 'user ' => [
1033+ 'info ' => [
1034+ 'country ' => 'IN '
1035+ ]
1036+ ]
1037+ ]
1038+ ]])
1039+ ]);
1040+ $ this ->assertCount (1 , $ results );
1041+ $ this ->assertEquals ('vecA ' , $ results [0 ]->getId ());
1042+
1043+ // 3) Contains on nested array inside metadata
1044+ $ results = $ database ->find ($ collectionId , [
1045+ Query::contains ('metadata ' , [[
1046+ 'tags ' => 'ml '
1047+ ]])
1048+ ]);
1049+ $ this ->assertCount (2 , $ results ); // vecA, vecC both have 'ml' in tags
1050+
1051+ // 4) Combine vector query with nested metadata filters
1052+ $ results = $ database ->find ($ collectionId , [
1053+ Query::vectorEuclidean ('embedding ' , [0.0 , 1.0 , 0.0 ]),
1054+ Query::equal ('metadata ' , [[
1055+ 'settings ' => [
1056+ 'prefs ' => [
1057+ 'theme ' => 'light '
1058+ ]
1059+ ]
1060+ ]]),
1061+ Query::limit (1 )
1062+ ]);
1063+ $ this ->assertCount (1 , $ results );
1064+ $ this ->assertEquals ('vecB ' , $ results [0 ]->getId ());
1065+
1066+ // 5) Deep partial containment with boolean nested value
1067+ $ results = $ database ->find ($ collectionId , [
1068+ Query::equal ('metadata ' , [[
1069+ 'settings ' => [
1070+ 'prefs ' => [
1071+ 'features ' => [
1072+ 'experimental ' => true
1073+ ]
1074+ ]
1075+ ]
1076+ ]])
1077+ ]);
1078+ $ this ->assertCount (1 , $ results );
1079+ $ this ->assertEquals ('vecA ' , $ results [0 ]->getId ());
1080+
1081+ // Cleanup
1082+ $ database ->deleteCollection ($ collectionId );
1083+ }
9281084}
0 commit comments