@@ -665,6 +665,284 @@ MCTF_TEST(test_art_random_delete)
665665 MCTF_FINISH ();
666666}
667667
668+ MCTF_TEST (test_art_prefix_search )
669+ {
670+ struct art * t = NULL ;
671+ char * * matches = NULL ;
672+ int count = 0 ;
673+
674+ pgmoneta_test_setup ();
675+
676+ pgmoneta_art_create (& t );
677+ MCTF_ASSERT_PTR_NONNULL (t , cleanup , "ART creation failed" );
678+
679+ MCTF_ASSERT (!pgmoneta_art_insert (t , "apple" , 1 , ValueInt32 ), cleanup , "Insert apple failed" );
680+ MCTF_ASSERT (!pgmoneta_art_insert (t , "applet" , 2 , ValueInt32 ), cleanup , "Insert applet failed" );
681+ MCTF_ASSERT (!pgmoneta_art_insert (t , "apply" , 3 , ValueInt32 ), cleanup , "Insert apply failed" );
682+ MCTF_ASSERT (!pgmoneta_art_insert (t , "ball" , 4 , ValueInt32 ), cleanup , "Insert ball failed" );
683+ MCTF_ASSERT (!pgmoneta_art_insert (t , "bat" , 5 , ValueInt32 ), cleanup , "Insert bat failed" );
684+
685+ count = pgmoneta_art_prefix_search (t , "app" , & matches , 10 );
686+ MCTF_ASSERT_INT_EQ (count , 3 , cleanup , "Prefix search 'app' should return 3 matches" );
687+ MCTF_ASSERT_STR_EQ (matches [0 ], "apple" , cleanup , "First match mismatch" );
688+ MCTF_ASSERT_STR_EQ (matches [1 ], "applet" , cleanup , "Second match mismatch" );
689+ MCTF_ASSERT_STR_EQ (matches [2 ], "apply" , cleanup , "Third match mismatch" );
690+ for (int i = 0 ; i < count ; i ++ )
691+ {
692+ free (matches [i ]);
693+ }
694+ free (matches );
695+ matches = NULL ;
696+
697+ count = pgmoneta_art_prefix_search (t , "cat" , & matches , 10 );
698+ MCTF_ASSERT_INT_EQ (count , 0 , cleanup , "Prefix search 'cat' should return 0 matches" );
699+ free (matches );
700+ matches = NULL ;
701+
702+ count = pgmoneta_art_prefix_search (t , "apple" , & matches , 10 );
703+ MCTF_ASSERT_INT_EQ (count , 2 , cleanup , "Prefix search 'apple' should return 2 matches" );
704+ MCTF_ASSERT_STR_EQ (matches [0 ], "apple" , cleanup , "First match mismatch" );
705+ MCTF_ASSERT_STR_EQ (matches [1 ], "applet" , cleanup , "Second match mismatch" );
706+ for (int i = 0 ; i < count ; i ++ )
707+ {
708+ free (matches [i ]);
709+ }
710+ free (matches );
711+ matches = NULL ;
712+
713+ count = pgmoneta_art_prefix_search (t , "" , & matches , 10 );
714+ MCTF_ASSERT_INT_EQ (count , 5 , cleanup , "Empty prefix should return all matches" );
715+ MCTF_ASSERT_STR_EQ (matches [0 ], "apple" , cleanup , "Match 0 mismatch" );
716+ MCTF_ASSERT_STR_EQ (matches [1 ], "applet" , cleanup , "Match 1 mismatch" );
717+ MCTF_ASSERT_STR_EQ (matches [2 ], "apply" , cleanup , "Match 2 mismatch" );
718+ MCTF_ASSERT_STR_EQ (matches [3 ], "ball" , cleanup , "Match 3 mismatch" );
719+ MCTF_ASSERT_STR_EQ (matches [4 ], "bat" , cleanup , "Match 4 mismatch" );
720+ for (int i = 0 ; i < count ; i ++ )
721+ {
722+ free (matches [i ]);
723+ }
724+ free (matches );
725+ matches = NULL ;
726+
727+ count = pgmoneta_art_prefix_search (t , "app" , & matches , 2 );
728+ MCTF_ASSERT_INT_EQ (count , 2 , cleanup , "Max matches should limit results to 2" );
729+ MCTF_ASSERT_STR_EQ (matches [0 ], "apple" , cleanup , "Match 0 mismatch" );
730+ MCTF_ASSERT_STR_EQ (matches [1 ], "applet" , cleanup , "Match 1 mismatch" );
731+ for (int i = 0 ; i < count ; i ++ )
732+ {
733+ free (matches [i ]);
734+ }
735+ free (matches );
736+ matches = NULL ;
737+
738+ count = pgmoneta_art_prefix_search (t , "applet" , & matches , 10 );
739+ MCTF_ASSERT_INT_EQ (count , 1 , cleanup , "Prefix search 'applet' should return exactly 1 match" );
740+ MCTF_ASSERT_STR_EQ (matches [0 ], "applet" , cleanup , "Exact match mismatch" );
741+ for (int i = 0 ; i < count ; i ++ )
742+ {
743+ free (matches [i ]);
744+ }
745+ free (matches );
746+ matches = NULL ;
747+
748+ MCTF_ASSERT (!pgmoneta_art_delete (t , "applet" ), cleanup , "Delete applet failed" );
749+ count = pgmoneta_art_prefix_search (t , "app" , & matches , 10 );
750+ MCTF_ASSERT_INT_EQ (count , 2 , cleanup , "Prefix search 'app' after delete should return 2 matches" );
751+ MCTF_ASSERT_STR_EQ (matches [0 ], "apple" , cleanup , "Match 0 mismatch after delete" );
752+ MCTF_ASSERT_STR_EQ (matches [1 ], "apply" , cleanup , "Match 1 mismatch after delete" );
753+ for (int i = 0 ; i < count ; i ++ )
754+ {
755+ free (matches [i ]);
756+ }
757+ free (matches );
758+ matches = NULL ;
759+
760+ count = pgmoneta_art_prefix_search (NULL , "app" , & matches , 10 );
761+ MCTF_ASSERT_INT_EQ (count , -1 , cleanup , "Prefix search with NULL tree should return -1" );
762+
763+ count = pgmoneta_art_prefix_search (t , "app" , NULL , 10 );
764+ MCTF_ASSERT_INT_EQ (count , -1 , cleanup , "Prefix search with NULL matches pointer should return -1" );
765+
766+ count = pgmoneta_art_prefix_search (t , "app" , & matches , 0 );
767+ MCTF_ASSERT_INT_EQ (count , -1 , cleanup , "Prefix search with 0 max_matches should return -1" );
768+
769+ cleanup :
770+ if (matches )
771+ {
772+ for (int i = 0 ; i < count ; i ++ )
773+ {
774+ free (matches [i ]);
775+ }
776+ free (matches );
777+ }
778+ pgmoneta_art_destroy (t );
779+ pgmoneta_test_teardown ();
780+ MCTF_FINISH ();
781+ }
782+
783+ MCTF_TEST (test_art_prefix_search_nested )
784+ {
785+ struct art * t = NULL ;
786+ char * * matches = NULL ;
787+ int count = 0 ;
788+
789+ pgmoneta_test_setup ();
790+ pgmoneta_art_create (& t );
791+ MCTF_ASSERT_PTR_NONNULL (t , cleanup , "ART creation failed" );
792+
793+ MCTF_ASSERT (!pgmoneta_art_insert (t , "api.foo.bar" , 1 , ValueInt32 ), cleanup , "Insert failed" );
794+ MCTF_ASSERT (!pgmoneta_art_insert (t , "api.foo.baz" , 2 , ValueInt32 ), cleanup , "Insert failed" );
795+ MCTF_ASSERT (!pgmoneta_art_insert (t , "api.foe.fum" , 3 , ValueInt32 ), cleanup , "Insert failed" );
796+ MCTF_ASSERT (!pgmoneta_art_insert (t , "abc.123.456" , 4 , ValueInt32 ), cleanup , "Insert failed" );
797+ MCTF_ASSERT (!pgmoneta_art_insert (t , "api.foo" , 5 , ValueInt32 ), cleanup , "Insert failed" );
798+ MCTF_ASSERT (!pgmoneta_art_insert (t , "api" , 6 , ValueInt32 ), cleanup , "Insert failed" );
799+
800+ count = pgmoneta_art_prefix_search (t , "api" , & matches , 10 );
801+ MCTF_ASSERT_INT_EQ (count , 5 , cleanup , "Prefix 'api' should return 5 matches" );
802+ MCTF_ASSERT_STR_EQ (matches [0 ], "api" , cleanup , "Match 0 mismatch" );
803+ MCTF_ASSERT_STR_EQ (matches [1 ], "api.foe.fum" , cleanup , "Match 1 mismatch" );
804+ MCTF_ASSERT_STR_EQ (matches [2 ], "api.foo" , cleanup , "Match 2 mismatch" );
805+ MCTF_ASSERT_STR_EQ (matches [3 ], "api.foo.bar" , cleanup , "Match 3 mismatch" );
806+ MCTF_ASSERT_STR_EQ (matches [4 ], "api.foo.baz" , cleanup , "Match 4 mismatch" );
807+ for (int i = 0 ; i < count ; i ++ )
808+ {
809+ free (matches [i ]);
810+ }
811+ free (matches );
812+ matches = NULL ;
813+
814+ count = pgmoneta_art_prefix_search (t , "a" , & matches , 10 );
815+ MCTF_ASSERT_INT_EQ (count , 6 , cleanup , "Prefix 'a' should return 6 matches" );
816+ for (int i = 0 ; i < count ; i ++ )
817+ {
818+ free (matches [i ]);
819+ }
820+ free (matches );
821+ matches = NULL ;
822+
823+ count = pgmoneta_art_prefix_search (t , "b" , & matches , 10 );
824+ MCTF_ASSERT_INT_EQ (count , 0 , cleanup , "Prefix 'b' should return 0 matches" );
825+ free (matches );
826+ matches = NULL ;
827+
828+ count = pgmoneta_art_prefix_search (t , "api." , & matches , 10 );
829+ MCTF_ASSERT_INT_EQ (count , 4 , cleanup , "Prefix 'api.' should return 4 matches" );
830+ for (int i = 0 ; i < count ; i ++ )
831+ {
832+ free (matches [i ]);
833+ }
834+ free (matches );
835+ matches = NULL ;
836+
837+ count = pgmoneta_art_prefix_search (t , "api.foo.ba" , & matches , 10 );
838+ MCTF_ASSERT_INT_EQ (count , 2 , cleanup , "Prefix 'api.foo.ba' should return 2 matches" );
839+ MCTF_ASSERT_STR_EQ (matches [0 ], "api.foo.bar" , cleanup , "Match 0 mismatch" );
840+ MCTF_ASSERT_STR_EQ (matches [1 ], "api.foo.baz" , cleanup , "Match 1 mismatch" );
841+ for (int i = 0 ; i < count ; i ++ )
842+ {
843+ free (matches [i ]);
844+ }
845+ free (matches );
846+ matches = NULL ;
847+
848+ count = pgmoneta_art_prefix_search (t , "api.end" , & matches , 10 );
849+ MCTF_ASSERT_INT_EQ (count , 0 , cleanup , "Prefix 'api.end' should return 0 matches" );
850+ free (matches );
851+ matches = NULL ;
852+
853+ cleanup :
854+ if (matches )
855+ {
856+ for (int i = 0 ; i < count ; i ++ )
857+ {
858+ free (matches [i ]);
859+ }
860+ free (matches );
861+ }
862+ pgmoneta_art_destroy (t );
863+ pgmoneta_test_teardown ();
864+ MCTF_FINISH ();
865+ }
866+
867+ MCTF_TEST (test_art_prefix_search_long_prefix )
868+ {
869+ struct art * t = NULL ;
870+ char * * matches = NULL ;
871+ int count = 0 ;
872+
873+ pgmoneta_test_setup ();
874+ pgmoneta_art_create (& t );
875+ MCTF_ASSERT_PTR_NONNULL (t , cleanup , "ART creation failed" );
876+
877+ MCTF_ASSERT (!pgmoneta_art_insert (t , "this:key:has:a:long:prefix:3" , 3 , ValueInt32 ), cleanup , "Insert failed" );
878+ MCTF_ASSERT (!pgmoneta_art_insert (t , "this:key:has:a:long:common:prefix:2" , 2 , ValueInt32 ), cleanup , "Insert failed" );
879+ MCTF_ASSERT (!pgmoneta_art_insert (t , "this:key:has:a:long:common:prefix:1" , 1 , ValueInt32 ), cleanup , "Insert failed" );
880+
881+ count = pgmoneta_art_prefix_search (t , "this:key:has" , & matches , 10 );
882+ MCTF_ASSERT_INT_EQ (count , 3 , cleanup , "Prefix 'this:key:has' should return 3 matches" );
883+ MCTF_ASSERT_STR_EQ (matches [0 ], "this:key:has:a:long:common:prefix:1" , cleanup , "Match 0 mismatch" );
884+ MCTF_ASSERT_STR_EQ (matches [1 ], "this:key:has:a:long:common:prefix:2" , cleanup , "Match 1 mismatch" );
885+ MCTF_ASSERT_STR_EQ (matches [2 ], "this:key:has:a:long:prefix:3" , cleanup , "Match 2 mismatch" );
886+ for (int i = 0 ; i < count ; i ++ )
887+ {
888+ free (matches [i ]);
889+ }
890+ free (matches );
891+ matches = NULL ;
892+
893+ cleanup :
894+ if (matches )
895+ {
896+ for (int i = 0 ; i < count ; i ++ )
897+ {
898+ free (matches [i ]);
899+ }
900+ free (matches );
901+ }
902+ pgmoneta_art_destroy (t );
903+ pgmoneta_test_teardown ();
904+ MCTF_FINISH ();
905+ }
906+
907+ MCTF_TEST (test_art_prefix_search_max_prefix_len )
908+ {
909+ struct art * t = NULL ;
910+ char * * matches = NULL ;
911+ int count = 0 ;
912+
913+ pgmoneta_test_setup ();
914+ pgmoneta_art_create (& t );
915+ MCTF_ASSERT_PTR_NONNULL (t , cleanup , "ART creation failed" );
916+
917+ MCTF_ASSERT (!pgmoneta_art_insert (t , "foobarbaz1-test1-foo" , 1 , ValueInt32 ), cleanup , "Insert failed" );
918+ MCTF_ASSERT (!pgmoneta_art_insert (t , "foobarbaz1-test1-bar" , 2 , ValueInt32 ), cleanup , "Insert failed" );
919+ MCTF_ASSERT (!pgmoneta_art_insert (t , "foobarbaz1-test2-foo" , 3 , ValueInt32 ), cleanup , "Insert failed" );
920+
921+ count = pgmoneta_art_prefix_search (t , "foobarbaz1-test1" , & matches , 10 );
922+ MCTF_ASSERT_INT_EQ (count , 2 , cleanup , "Prefix 'foobarbaz1-test1' should return 2 matches" );
923+ MCTF_ASSERT_STR_EQ (matches [0 ], "foobarbaz1-test1-bar" , cleanup , "Match 0 mismatch" );
924+ MCTF_ASSERT_STR_EQ (matches [1 ], "foobarbaz1-test1-foo" , cleanup , "Match 1 mismatch" );
925+ for (int i = 0 ; i < count ; i ++ )
926+ {
927+ free (matches [i ]);
928+ }
929+ free (matches );
930+ matches = NULL ;
931+
932+ cleanup :
933+ if (matches )
934+ {
935+ for (int i = 0 ; i < count ; i ++ )
936+ {
937+ free (matches [i ]);
938+ }
939+ free (matches );
940+ }
941+ pgmoneta_art_destroy (t );
942+ pgmoneta_test_teardown ();
943+ MCTF_FINISH ();
944+ }
945+
668946MCTF_TEST (test_art_insert_index_out_of_range )
669947{
670948 struct art * t = NULL ;
0 commit comments