@@ -546,6 +546,222 @@ order by SessionId;)", "%Y-%m-%d %H:%M:%S %Z", sessionsSet.front().GetId().data(
546546 CompareYson (expected, StreamResultToYson (it));
547547 }
548548
549+ Y_UNIT_TEST (NodesOrderByDesc) {
550+ // Test to reproduce issue #12585: ORDER BY DESC doesn't work for sys views
551+ // The sys view actors ignore the direction flag and don't guarantee order
552+ TKikimrRunner kikimr (" " , KikimrDefaultUtDomainRoot, 5 );
553+ auto client = kikimr.GetQueryClient ();
554+ auto session = client.GetSession ().GetValueSync ().GetSession ();
555+
556+ ui32 offset = kikimr.GetTestServer ().GetRuntime ()->GetNodeId (0 );
557+
558+ auto result = session.ExecuteQuery (R"( --!syntax_v1
559+ SELECT NodeId, Host
560+ FROM `/Root/.sys/nodes`
561+ ORDER BY NodeId DESC
562+ )" , NYdb::NQuery::TTxControl::NoTx ()).GetValueSync ();
563+
564+ UNIT_ASSERT_C (result.IsSuccess (), result.GetIssues ().ToString ());
565+
566+ // Collect all results
567+ TVector<ui32> nodeIds;
568+ auto resultSet = result.GetResultSet (0 );
569+ NYdb::TResultSetParser parser (resultSet);
570+ while (parser.TryNextRow ()) {
571+ auto nodeId = parser.ColumnParser (" NodeId" ).GetOptionalUint32 ().value ();
572+ nodeIds.push_back (nodeId);
573+ }
574+
575+ // Verify we got all 5 nodes
576+ UNIT_ASSERT_VALUES_EQUAL (nodeIds.size (), 5 );
577+
578+ // Verify results are in descending order (this should fail if the bug exists)
579+ // According to issue #12585, sys view actors ignore the direction flag
580+ // and don't guarantee order, so this assertion should fail
581+ for (size_t i = 1 ; i < nodeIds.size (); ++i) {
582+ UNIT_ASSERT_C (nodeIds[i - 1 ] >= nodeIds[i],
583+ TStringBuilder () << " Results not in descending order: "
584+ << " nodeIds[" << (i - 1 ) << " ] = " << nodeIds[i - 1 ]
585+ << " < nodeIds[" << i << " ] = " << nodeIds[i]
586+ << " . ORDER BY DESC is being ignored by sys view actors." );
587+ }
588+
589+ // Verify exact expected order: offset+4, offset+3, offset+2, offset+1, offset
590+ UNIT_ASSERT_VALUES_EQUAL (nodeIds[0 ], offset + 4 );
591+ UNIT_ASSERT_VALUES_EQUAL (nodeIds[1 ], offset + 3 );
592+ UNIT_ASSERT_VALUES_EQUAL (nodeIds[2 ], offset + 2 );
593+ UNIT_ASSERT_VALUES_EQUAL (nodeIds[3 ], offset + 1 );
594+ UNIT_ASSERT_VALUES_EQUAL (nodeIds[4 ], offset);
595+ }
596+
597+ Y_UNIT_TEST (PartitionStatsOrderByDesc) {
598+ // Test ORDER BY DESC for partition_stats sys view
599+ // Primary key: OwnerId, PathId, PartIdx, FollowerId
600+ TKikimrRunner kikimr;
601+ auto client = kikimr.GetQueryClient ();
602+ auto session = client.GetSession ().GetValueSync ().GetSession ();
603+
604+ auto result = session.ExecuteQuery (R"( --!syntax_v1
605+ SELECT OwnerId, PathId, PartIdx, FollowerId, Path
606+ FROM `/Root/.sys/partition_stats`
607+ ORDER BY OwnerId DESC, PathId DESC, PartIdx DESC, FollowerId DESC
608+ )" , NYdb::NQuery::TTxControl::NoTx ()).GetValueSync ();
609+
610+ UNIT_ASSERT_C (result.IsSuccess (), result.GetIssues ().ToString ());
611+
612+ // Collect all results
613+ struct TPartitionKey {
614+ ui64 OwnerId;
615+ ui64 PathId;
616+ ui64 PartIdx;
617+ ui32 FollowerId;
618+ };
619+ TVector<TPartitionKey> partitionKeys;
620+ auto resultSet = result.GetResultSet (0 );
621+ NYdb::TResultSetParser parser (resultSet);
622+ while (parser.TryNextRow ()) {
623+ auto ownerId = parser.ColumnParser (" OwnerId" ).GetOptionalUint64 ().value ();
624+ auto pathId = parser.ColumnParser (" PathId" ).GetOptionalUint64 ().value ();
625+ auto partIdx = parser.ColumnParser (" PartIdx" ).GetOptionalUint64 ().value ();
626+ auto followerId = parser.ColumnParser (" FollowerId" ).GetOptionalUint32 ().value ();
627+ partitionKeys.push_back ({ownerId, pathId, partIdx, followerId});
628+ }
629+
630+ // Verify we got some results
631+ UNIT_ASSERT_C (partitionKeys.size () > 0 , " Expected at least one partition" );
632+
633+ // Verify results are in descending order by OwnerId, PathId, PartIdx, FollowerId
634+ for (size_t i = 1 ; i < partitionKeys.size (); ++i) {
635+ const auto & prev = partitionKeys[i - 1 ];
636+ const auto & curr = partitionKeys[i];
637+
638+ auto prevKey = std::tie (prev.OwnerId , prev.PathId , prev.PartIdx , prev.FollowerId );
639+ auto currKey = std::tie (curr.OwnerId , curr.PathId , curr.PartIdx , curr.FollowerId );
640+
641+ UNIT_ASSERT_C (prevKey >= currKey,
642+ TStringBuilder () << " Results not in descending order: "
643+ << " partitionKeys[" << (i - 1 ) << " ] = (" << prev.OwnerId << " , " << prev.PathId << " , " << prev.PartIdx << " , " << prev.FollowerId << " )"
644+ << " < partitionKeys[" << i << " ] = (" << curr.OwnerId << " , " << curr.PathId << " , " << curr.PartIdx << " , " << curr.FollowerId << " )"
645+ << " . ORDER BY DESC is being ignored by sys view actors." );
646+ }
647+ }
648+
649+ Y_UNIT_TEST (QuerySessionsOrderByDesc) {
650+ // Test ORDER BY DESC for query_sessions sys view
651+ NKikimrConfig::TFeatureFlags featureFlags;
652+ featureFlags.SetEnableRealSystemViewPaths (true );
653+ TKikimrSettings settings;
654+ settings.SetWithSampleTables (false );
655+ settings.SetFeatureFlags (featureFlags);
656+ settings.SetAuthToken (" root@builtin" );
657+ TKikimrRunner kikimr (settings);
658+
659+ auto client = kikimr.GetQueryClient ();
660+ auto session = client.GetSession ().GetValueSync ().GetSession ();
661+
662+ // Create some sessions to have data
663+ const size_t sessionsCount = 5 ;
664+ std::vector<NYdb::NQuery::TSession> sessionsSet;
665+ for (ui32 i = 0 ; i < sessionsCount; i++) {
666+ sessionsSet.emplace_back (std::move (client.GetSession ().GetValueSync ().GetSession ()));
667+ }
668+
669+ // Wait a bit for sessions to be registered
670+ ::Sleep (TDuration::MilliSeconds(100 ));
671+
672+ auto result = session.ExecuteQuery (R"( --!syntax_v1
673+ SELECT SessionId, State, NodeId
674+ FROM `/Root/.sys/query_sessions`
675+ ORDER BY SessionId DESC
676+ )" , NYdb::NQuery::TTxControl::NoTx ()).GetValueSync ();
677+
678+ UNIT_ASSERT_C (result.IsSuccess (), result.GetIssues ().ToString ());
679+
680+ // Collect all results
681+ TVector<std::string> sessionIds;
682+ auto resultSet = result.GetResultSet (0 );
683+ NYdb::TResultSetParser parser (resultSet);
684+ while (parser.TryNextRow ()) {
685+ auto sessionId = parser.ColumnParser (" SessionId" ).GetOptionalUtf8 ().value ();
686+ sessionIds.push_back (sessionId);
687+ }
688+
689+ // Verify we got some results
690+ UNIT_ASSERT_C (sessionIds.size () > 0 , " Expected at least one session" );
691+
692+ // Verify results are in descending order (lexicographically)
693+ for (size_t i = 1 ; i < sessionIds.size (); ++i) {
694+ UNIT_ASSERT_C (sessionIds[i - 1 ] >= sessionIds[i],
695+ TStringBuilder () << " Results not in descending order: "
696+ << " sessionIds[" << (i - 1 ) << " ] = \" " << sessionIds[i - 1 ] << " \" "
697+ << " < sessionIds[" << i << " ] = \" " << sessionIds[i] << " \" "
698+ << " . ORDER BY DESC is being ignored by sys view actors." );
699+ }
700+ }
701+
702+ Y_UNIT_TEST (TopQueriesOrderByDesc) {
703+ // Test ORDER BY DESC for top_queries sys views
704+ TKikimrRunner kikimr (" " , KikimrDefaultUtDomainRoot, 3 );
705+ auto client = kikimr.GetQueryClient ();
706+ auto session = client.GetSession ().GetValueSync ().GetSession ();
707+
708+ // Execute some queries to populate stats
709+ auto tableClient = kikimr.GetTableClient ();
710+ auto tableSession = tableClient.CreateSession ().GetValueSync ().GetSession ();
711+ {
712+ auto result = tableSession.ExecuteDataQuery (Q_ (R"(
713+ SELECT * FROM `/Root/TwoShard`
714+ )" ), TTxControl::BeginTx ().CommitTx ()).GetValueSync ();
715+ UNIT_ASSERT_VALUES_EQUAL (result.GetStatus (), EStatus::SUCCESS);
716+ }
717+ {
718+ auto result = tableSession.ExecuteDataQuery (Q_ (R"(
719+ SELECT * FROM `/Root/EightShard`
720+ )" ), TTxControl::BeginTx ().CommitTx ()).GetValueSync ();
721+ UNIT_ASSERT_VALUES_EQUAL (result.GetStatus (), EStatus::SUCCESS);
722+ }
723+
724+ // Wait a bit for stats to be collected
725+ ::Sleep (TDuration::MilliSeconds(500 ));
726+
727+ // Test top_queries_by_read_bytes_one_minute
728+ auto result = session.ExecuteQuery (R"( --!syntax_v1
729+ SELECT IntervalEnd, Rank, ReadBytes
730+ FROM `/Root/.sys/top_queries_by_read_bytes_one_minute`
731+ ORDER BY IntervalEnd DESC, Rank DESC
732+ )" , NYdb::NQuery::TTxControl::NoTx ()).GetValueSync ();
733+
734+ UNIT_ASSERT_C (result.IsSuccess (), result.GetIssues ().ToString ());
735+
736+ // Collect all results
737+ TVector<std::pair<TInstant, ui32>> intervalEndRank;
738+ auto resultSet = result.GetResultSet (0 );
739+ NYdb::TResultSetParser parser (resultSet);
740+ while (parser.TryNextRow ()) {
741+ auto intervalEnd = parser.ColumnParser (" IntervalEnd" ).GetOptionalTimestamp ().value ();
742+ auto rank = parser.ColumnParser (" Rank" ).GetOptionalUint32 ().value ();
743+ intervalEndRank.push_back ({intervalEnd, rank});
744+ }
745+
746+ // Verify we got some results (may be empty if no stats collected yet)
747+ if (intervalEndRank.size () > 0 ) {
748+ // Verify results are in descending order by IntervalEnd, then Rank
749+ for (size_t i = 1 ; i < intervalEndRank.size (); ++i) {
750+ const auto & prev = intervalEndRank[i - 1 ];
751+ const auto & curr = intervalEndRank[i];
752+
753+ auto prevKey = std::tie (prev.first , prev.second );
754+ auto currKey = std::tie (curr.first , curr.second );
755+
756+ UNIT_ASSERT_C (prevKey >= currKey,
757+ TStringBuilder () << " Results not in descending order: "
758+ << " intervalEndRank[" << (i - 1 ) << " ] = (" << prev.first .ToString () << " , " << prev.second << " )"
759+ << " < intervalEndRank[" << i << " ] = (" << curr.first .ToString () << " , " << curr.second << " )"
760+ << " . ORDER BY DESC is being ignored by sys view actors." );
761+ }
762+ }
763+ }
764+
549765 Y_UNIT_TEST (QueryStatsSimple) {
550766 auto checkTable = [&] (const TStringBuf tableName) {
551767 TKikimrRunner kikimr (" " , KikimrDefaultUtDomainRoot, 3 );
0 commit comments