4
4
5
5
use Vladvildanov \PredisVl \Enum \Condition ;
6
6
use Vladvildanov \PredisVl \Enum \Logical ;
7
+ use Vladvildanov \PredisVl \Enum \Unit ;
7
8
use Vladvildanov \PredisVl \Feature \FeatureTestCase ;
8
9
use Predis \Client ;
9
10
use Vladvildanov \PredisVl \Index \SearchIndex ;
10
11
use Vladvildanov \PredisVl \Query \Filter \FilterInterface ;
12
+ use Vladvildanov \PredisVl \Query \Filter \GeoFilter ;
11
13
use Vladvildanov \PredisVl \Query \Filter \NumericFilter ;
12
14
use Vladvildanov \PredisVl \Query \Filter \TagFilter ;
13
15
use Vladvildanov \PredisVl \Query \Filter \TextFilter ;
@@ -515,6 +517,94 @@ public function testVectorQueryHashWithTextFilter(
515
517
}
516
518
}
517
519
520
+ /**
521
+ * @dataProvider vectorGeoFilterProvider
522
+ * @param FilterInterface|null $filter
523
+ * @param array $expectedResponse
524
+ * @return void
525
+ */
526
+ public function testVectorQueryHashWithGeoFilter (
527
+ ?FilterInterface $ filter ,
528
+ array $ expectedResponse
529
+ ): void {
530
+ $ schema = [
531
+ 'index ' => [
532
+ 'name ' => 'products ' ,
533
+ 'prefix ' => 'product: ' ,
534
+ ],
535
+ 'fields ' => [
536
+ 'id ' => [
537
+ 'type ' => 'text ' ,
538
+ ],
539
+ 'price ' => [
540
+ 'type ' => 'numeric ' ,
541
+ ],
542
+ 'location ' => [
543
+ 'type ' => 'geo ' ,
544
+ ],
545
+ 'description_embedding ' => [
546
+ 'type ' => 'vector ' ,
547
+ 'dims ' => 3 ,
548
+ 'datatype ' => 'float32 ' ,
549
+ 'algorithm ' => 'flat ' ,
550
+ 'distance_metric ' => 'cosine '
551
+ ],
552
+ ],
553
+ ];
554
+
555
+ $ index = new SearchIndex ($ this ->client , $ schema );
556
+ $ this ->assertEquals ('OK ' , $ index ->create ());
557
+
558
+ $ this ->assertTrue ($ index ->load (
559
+ '1 ' ,
560
+ [
561
+ 'id ' => '1 ' , 'price ' => 10 , 'location ' => '10.111,11.111 ' ,
562
+ 'description_embedding ' => VectorHelper::toBytes ([0.001 , 0.002 , 0.003 ])
563
+ ])
564
+ );
565
+ $ this ->assertTrue ($ index ->load (
566
+ '2 ' ,
567
+ [
568
+ 'id ' => '2 ' , 'price ' => 20 , 'location ' => '10.222,11.222 ' ,
569
+ 'description_embedding ' => VectorHelper::toBytes ([0.001 , 0.002 , 0.003 ])
570
+ ])
571
+ );
572
+ $ this ->assertTrue ($ index ->load (
573
+ '3 ' ,
574
+ [
575
+ 'id ' => '3 ' , 'price ' => 30 , 'location ' => '10.333,11.333 ' ,
576
+ 'description_embedding ' => VectorHelper::toBytes ([0.001 , 0.002 , 0.003 ])
577
+ ])
578
+ );
579
+ $ this ->assertTrue ($ index ->load (
580
+ '4 ' ,
581
+ [
582
+ 'id ' => '4 ' , 'price ' => 40 , 'location ' => '10.444,11.444 ' ,
583
+ 'description_embedding ' => VectorHelper::toBytes ([0.001 , 0.002 , 0.003 ])
584
+ ])
585
+ );
586
+
587
+ $ query = new VectorQuery (
588
+ [0.001 , 0.002 , 0.03 ],
589
+ 'description_embedding ' ,
590
+ null ,
591
+ 10 ,
592
+ true ,
593
+ 2 ,
594
+ $ filter
595
+ );
596
+
597
+ $ response = $ index ->query ($ query );
598
+ $ this ->assertSame ($ expectedResponse ['count ' ], $ response ['count ' ]);
599
+
600
+ foreach ($ expectedResponse ['results ' ] as $ key => $ value ) {
601
+ $ this ->assertSame (
602
+ $ expectedResponse ['results ' ][$ key ]['location ' ],
603
+ $ response ['results ' ][$ key ]['location ' ]
604
+ );
605
+ }
606
+ }
607
+
518
608
/**
519
609
* @return void
520
610
*/
@@ -925,6 +1015,100 @@ public function testVectorQueryJsonIndexWithTextFilter(
925
1015
}
926
1016
}
927
1017
1018
+ /**
1019
+ * @dataProvider vectorGeoFilterProvider
1020
+ * @param FilterInterface|null $filter
1021
+ * @param array $expectedResponse
1022
+ * @return void
1023
+ * @throws \JsonException
1024
+ */
1025
+ public function testVectorQueryJsonIndexWithGeoFilter (
1026
+ ?FilterInterface $ filter ,
1027
+ array $ expectedResponse
1028
+ ): void {
1029
+ $ schema = [
1030
+ 'index ' => [
1031
+ 'name ' => 'products ' ,
1032
+ 'prefix ' => 'product: ' ,
1033
+ 'storage_type ' => 'json '
1034
+ ],
1035
+ 'fields ' => [
1036
+ '$.id ' => [
1037
+ 'type ' => 'text ' ,
1038
+ ],
1039
+ '$.price ' => [
1040
+ 'type ' => 'numeric ' ,
1041
+ ],
1042
+ '$.location ' => [
1043
+ 'type ' => 'geo ' ,
1044
+ 'alias ' => 'location ' ,
1045
+ ],
1046
+ '$.description_embedding ' => [
1047
+ 'type ' => 'vector ' ,
1048
+ 'dims ' => 3 ,
1049
+ 'datatype ' => 'float32 ' ,
1050
+ 'algorithm ' => 'flat ' ,
1051
+ 'distance_metric ' => 'cosine ' ,
1052
+ 'alias ' => 'vector_embedding ' ,
1053
+ ],
1054
+ ],
1055
+ ];
1056
+
1057
+ $ index = new SearchIndex ($ this ->client , $ schema );
1058
+ $ this ->assertEquals ('OK ' , $ index ->create ());
1059
+
1060
+ $ this ->assertTrue ($ index ->load (
1061
+ '1 ' ,
1062
+ json_encode ([
1063
+ 'id ' => '1 ' , 'price ' => 10 , 'location ' => ['10.111,11.111 ' ],
1064
+ 'description_embedding ' => [0.001 , 0.002 , 0.003 ],
1065
+ ], JSON_THROW_ON_ERROR )
1066
+ ));
1067
+ $ this ->assertTrue ($ index ->load (
1068
+ '2 ' ,
1069
+ json_encode ([
1070
+ 'id ' => '2 ' , 'price ' => 20 , 'location ' => ['10.222,11.222 ' ],
1071
+ 'description_embedding ' => [0.001 , 0.002 , 0.003 ],
1072
+ ], JSON_THROW_ON_ERROR )
1073
+ ));
1074
+ $ this ->assertTrue ($ index ->load (
1075
+ '3 ' ,
1076
+ json_encode ([
1077
+ 'id ' => '3 ' , 'price ' => 30 , 'location ' => ['10.333,11.333 ' ],
1078
+ 'description_embedding ' => [0.001 , 0.002 , 0.003 ],
1079
+ ], JSON_THROW_ON_ERROR )
1080
+ ));
1081
+ $ this ->assertTrue ($ index ->load (
1082
+ '4 ' ,
1083
+ json_encode ([
1084
+ 'id ' => '4 ' , 'price ' => 40 , 'location ' => ['10.444,11.444 ' ],
1085
+ 'description_embedding ' => [0.001 , 0.002 , 0.003 ],
1086
+ ], JSON_THROW_ON_ERROR )
1087
+ ));
1088
+
1089
+ $ query = new VectorQuery (
1090
+ [0.001 , 0.002 , 0.03 ],
1091
+ 'vector_embedding ' ,
1092
+ null ,
1093
+ 10 ,
1094
+ true ,
1095
+ 2 ,
1096
+ $ filter
1097
+ );
1098
+
1099
+ $ response = $ index ->query ($ query );
1100
+ $ this ->assertSame ($ expectedResponse ['count ' ], $ response ['count ' ]);
1101
+
1102
+ foreach ($ response ['results ' ] as $ key => $ value ) {
1103
+ $ decodedResponse = json_decode ($ value ['$ ' ], true , 512 , JSON_THROW_ON_ERROR );
1104
+
1105
+ $ this ->assertSame (
1106
+ $ expectedResponse ['results ' ][$ key ]['location ' ],
1107
+ $ decodedResponse ['location ' ][0 ]
1108
+ );
1109
+ }
1110
+ }
1111
+
928
1112
/**
929
1113
* @return void
930
1114
* @throws \JsonException
@@ -1484,4 +1668,66 @@ public static function vectorTextFilterProvider(): array
1484
1668
],
1485
1669
];
1486
1670
}
1671
+
1672
+ public static function vectorGeoFilterProvider (): array
1673
+ {
1674
+ return [
1675
+ 'default ' => [
1676
+ null ,
1677
+ [
1678
+ 'count ' => 4 ,
1679
+ 'results ' => [
1680
+ 'product:1 ' => [
1681
+ 'location ' => '10.111,11.111 ' ,
1682
+ ],
1683
+ 'product:2 ' => [
1684
+ 'location ' => '10.222,11.222 ' ,
1685
+ ],
1686
+ 'product:3 ' => [
1687
+ 'location ' => '10.333,11.333 ' ,
1688
+ ],
1689
+ 'product:4 ' => [
1690
+ 'location ' => '10.444,11.444 ' ,
1691
+ ],
1692
+ ]
1693
+ ]
1694
+ ],
1695
+ 'equal_radius ' => [
1696
+ new GeoFilter (
1697
+ 'location ' ,
1698
+ Condition::equal,
1699
+ ['lon ' => 10.000 , 'lat ' => 12.000 , 'radius ' => 85 , 'unit ' => Unit::kilometers]
1700
+ ),
1701
+ [
1702
+ 'count ' => 2 ,
1703
+ 'results ' => [
1704
+ 'product:3 ' => [
1705
+ 'location ' => '10.333,11.333 ' ,
1706
+ ],
1707
+ 'product:4 ' => [
1708
+ 'location ' => '10.444,11.444 ' ,
1709
+ ],
1710
+ ]
1711
+ ]
1712
+ ],
1713
+ 'not_equal_radius ' => [
1714
+ new GeoFilter (
1715
+ 'location ' ,
1716
+ Condition::notEqual,
1717
+ ['lon ' => 10.000 , 'lat ' => 12.000 , 'radius ' => 85 , 'unit ' => Unit::kilometers]
1718
+ ),
1719
+ [
1720
+ 'count ' => 2 ,
1721
+ 'results ' => [
1722
+ 'product:1 ' => [
1723
+ 'location ' => '10.111,11.111 ' ,
1724
+ ],
1725
+ 'product:2 ' => [
1726
+ 'location ' => '10.222,11.222 ' ,
1727
+ ],
1728
+ ]
1729
+ ]
1730
+ ],
1731
+ ];
1732
+ }
1487
1733
}
0 commit comments