@@ -564,6 +564,291 @@ order by SessionId;)", "%Y-%m-%d %H:%M:%S %Z", sessionsSet.front().GetId().data(
564564 CompareYson (expected, StreamResultToYson (it));
565565 }
566566
567+ Y_UNIT_TEST (NodesOrderByDesc) {
568+ // Test to reproduce issue #12585: ORDER BY DESC doesn't work for sys views
569+ // The sys view actors ignore the direction flag and don't guarantee order
570+ TKikimrRunner kikimr (" " , KikimrDefaultUtDomainRoot, 5 );
571+ auto client = kikimr.GetQueryClient ();
572+ auto session = client.GetSession ().GetValueSync ().GetSession ();
573+
574+ ui32 offset = kikimr.GetTestServer ().GetRuntime ()->GetNodeId (0 );
575+
576+ auto result = session.ExecuteQuery (R"( --!syntax_v1
577+ SELECT NodeId, Host
578+ FROM `/Root/.sys/nodes`
579+ ORDER BY NodeId DESC
580+ )" , NYdb::NQuery::TTxControl::NoTx ()).GetValueSync ();
581+
582+ UNIT_ASSERT_C (result.IsSuccess (), result.GetIssues ().ToString ());
583+
584+ // Collect all results
585+ TVector<ui32> nodeIds;
586+ auto resultSet = result.GetResultSet (0 );
587+ NYdb::TResultSetParser parser (resultSet);
588+ while (parser.TryNextRow ()) {
589+ auto nodeId = parser.ColumnParser (" NodeId" ).GetOptionalUint32 ().value ();
590+ nodeIds.push_back (nodeId);
591+ }
592+
593+ // Verify we got all 5 nodes
594+ UNIT_ASSERT_VALUES_EQUAL (nodeIds.size (), 5 );
595+
596+ // Verify results are in descending order (this should fail if the bug exists)
597+ // According to issue #12585, sys view actors ignore the direction flag
598+ // and don't guarantee order, so this assertion should fail
599+ for (size_t i = 1 ; i < nodeIds.size (); ++i) {
600+ UNIT_ASSERT_C (nodeIds[i - 1 ] >= nodeIds[i],
601+ TStringBuilder () << " Results not in descending order: "
602+ << " nodeIds[" << (i - 1 ) << " ] = " << nodeIds[i - 1 ]
603+ << " < nodeIds[" << i << " ] = " << nodeIds[i]
604+ << " . ORDER BY DESC is being ignored by sys view actors." );
605+ }
606+
607+ // Verify exact expected order: offset+4, offset+3, offset+2, offset+1, offset
608+ UNIT_ASSERT_VALUES_EQUAL (nodeIds[0 ], offset + 4 );
609+ UNIT_ASSERT_VALUES_EQUAL (nodeIds[1 ], offset + 3 );
610+ UNIT_ASSERT_VALUES_EQUAL (nodeIds[2 ], offset + 2 );
611+ UNIT_ASSERT_VALUES_EQUAL (nodeIds[3 ], offset + 1 );
612+ UNIT_ASSERT_VALUES_EQUAL (nodeIds[4 ], offset);
613+ }
614+
615+ Y_UNIT_TEST (PartitionStatsOrderByDesc) {
616+ // Test ORDER BY DESC for partition_stats sys view
617+ // Primary key: OwnerId, PathId, PartIdx, FollowerId
618+ TKikimrRunner kikimr;
619+ auto client = kikimr.GetQueryClient ();
620+ auto session = client.GetSession ().GetValueSync ().GetSession ();
621+
622+ auto result = session.ExecuteQuery (R"( --!syntax_v1
623+ SELECT OwnerId, PathId, PartIdx, FollowerId, Path
624+ FROM `/Root/.sys/partition_stats`
625+ ORDER BY OwnerId DESC, PathId DESC, PartIdx DESC, FollowerId DESC
626+ )" , NYdb::NQuery::TTxControl::NoTx ()).GetValueSync ();
627+
628+ UNIT_ASSERT_C (result.IsSuccess (), result.GetIssues ().ToString ());
629+
630+ // Collect all results
631+ struct TPartitionKey {
632+ ui64 OwnerId;
633+ ui64 PathId;
634+ ui64 PartIdx;
635+ ui32 FollowerId;
636+ };
637+ TVector<TPartitionKey> partitionKeys;
638+ auto resultSet = result.GetResultSet (0 );
639+ NYdb::TResultSetParser parser (resultSet);
640+ while (parser.TryNextRow ()) {
641+ auto ownerId = parser.ColumnParser (" OwnerId" ).GetOptionalUint64 ().value ();
642+ auto pathId = parser.ColumnParser (" PathId" ).GetOptionalUint64 ().value ();
643+ auto partIdx = parser.ColumnParser (" PartIdx" ).GetOptionalUint64 ().value ();
644+ auto followerId = parser.ColumnParser (" FollowerId" ).GetOptionalUint32 ().value ();
645+ partitionKeys.push_back ({ownerId, pathId, partIdx, followerId});
646+ }
647+
648+ // Verify we got some results
649+ UNIT_ASSERT_C (partitionKeys.size () > 0 , " Expected at least one partition" );
650+
651+ // Verify results are in descending order by OwnerId, PathId, PartIdx, FollowerId
652+ for (size_t i = 1 ; i < partitionKeys.size (); ++i) {
653+ const auto & prev = partitionKeys[i - 1 ];
654+ const auto & curr = partitionKeys[i];
655+
656+ auto prevKey = std::tie (prev.OwnerId , prev.PathId , prev.PartIdx , prev.FollowerId );
657+ auto currKey = std::tie (curr.OwnerId , curr.PathId , curr.PartIdx , curr.FollowerId );
658+
659+ UNIT_ASSERT_C (prevKey >= currKey,
660+ TStringBuilder () << " Results not in descending order: "
661+ << " partitionKeys[" << (i - 1 ) << " ] = (" << prev.OwnerId << " , " << prev.PathId << " , " << prev.PartIdx << " , " << prev.FollowerId << " )"
662+ << " < partitionKeys[" << i << " ] = (" << curr.OwnerId << " , " << curr.PathId << " , " << curr.PartIdx << " , " << curr.FollowerId << " )"
663+ << " . ORDER BY DESC is being ignored by sys view actors." );
664+ }
665+ }
666+
667+ Y_UNIT_TEST (QuerySessionsOrderByDesc) {
668+ // Test ORDER BY DESC for query_sessions sys view
669+ NKikimrConfig::TFeatureFlags featureFlags;
670+ featureFlags.SetEnableRealSystemViewPaths (true );
671+ TKikimrSettings settings;
672+ settings.SetWithSampleTables (false );
673+ settings.SetFeatureFlags (featureFlags);
674+ settings.SetAuthToken (" root@builtin" );
675+ TKikimrRunner kikimr (settings);
676+
677+ auto client = kikimr.GetQueryClient ();
678+ auto session = client.GetSession ().GetValueSync ().GetSession ();
679+
680+ // Create some sessions to have data
681+ const size_t sessionsCount = 5 ;
682+ std::vector<NYdb::NQuery::TSession> sessionsSet;
683+ for (ui32 i = 0 ; i < sessionsCount; i++) {
684+ sessionsSet.emplace_back (std::move (client.GetSession ().GetValueSync ().GetSession ()));
685+ }
686+
687+ // Wait a bit for sessions to be registered
688+ ::Sleep (TDuration::MilliSeconds(100 ));
689+
690+ auto result = session.ExecuteQuery (R"( --!syntax_v1
691+ SELECT SessionId, State, NodeId
692+ FROM `/Root/.sys/query_sessions`
693+ ORDER BY SessionId DESC
694+ )" , NYdb::NQuery::TTxControl::NoTx ()).GetValueSync ();
695+
696+ UNIT_ASSERT_C (result.IsSuccess (), result.GetIssues ().ToString ());
697+
698+ // Collect all results
699+ TVector<std::string> sessionIds;
700+ auto resultSet = result.GetResultSet (0 );
701+ NYdb::TResultSetParser parser (resultSet);
702+ while (parser.TryNextRow ()) {
703+ auto sessionId = parser.ColumnParser (" SessionId" ).GetOptionalUtf8 ().value ();
704+ sessionIds.push_back (sessionId);
705+ }
706+
707+ // Verify we got some results
708+ UNIT_ASSERT_C (sessionIds.size () > 0 , " Expected at least one session" );
709+
710+ // Verify results are in descending order (lexicographically)
711+ for (size_t i = 1 ; i < sessionIds.size (); ++i) {
712+ UNIT_ASSERT_C (sessionIds[i - 1 ] >= sessionIds[i],
713+ TStringBuilder () << " Results not in descending order: "
714+ << " sessionIds[" << (i - 1 ) << " ] = \" " << sessionIds[i - 1 ] << " \" "
715+ << " < sessionIds[" << i << " ] = \" " << sessionIds[i] << " \" "
716+ << " . ORDER BY DESC is being ignored by sys view actors." );
717+ }
718+ }
719+
720+ Y_UNIT_TEST (CompileCacheQueriesOrderByDesc) {
721+ // Test ORDER BY DESC for compile_cache_queries sys view
722+ // Primary key: NodeId, QueryId
723+ auto serverSettings = TKikimrSettings ().SetKqpSettings ({ NKikimrKqp::TKqpSetting () });
724+ serverSettings.AppConfig .MutableTableServiceConfig ()->SetEnableImplicitQueryParameterTypes (true );
725+ TKikimrRunner kikimr (serverSettings);
726+ kikimr.GetTestServer ().GetRuntime ()->GetAppData ().FeatureFlags .SetEnableCompileCacheView (true );
727+ auto client = kikimr.GetQueryClient ();
728+ auto session = client.GetSession ().GetValueSync ().GetSession ();
729+
730+ // Execute some queries to populate compile cache
731+ auto tableClient = kikimr.GetTableClient ();
732+ for (ui32 i = 0 ; i < 3 ; ++i) {
733+ auto tableSession = tableClient.CreateSession ().GetValueSync ().GetSession ();
734+ auto paramsBuilder = TParamsBuilder ();
735+ paramsBuilder.AddParam (" $k" ).Uint64 (i).Build ();
736+ auto executedResult = tableSession.ExecuteDataQuery (
737+ R"( DECLARE $k AS Uint64;
738+ SELECT COUNT(*) FROM `/Root/EightShard` WHERE Key = $k;)" ,
739+ TTxControl::BeginTx ().CommitTx (),
740+ paramsBuilder.Build ()
741+ ).GetValueSync ();
742+ UNIT_ASSERT_C (executedResult.IsSuccess (), executedResult.GetIssues ().ToString ());
743+ }
744+
745+ // Wait a bit for compile cache to be updated
746+ ::Sleep (TDuration::MilliSeconds(100 ));
747+
748+ auto result = session.ExecuteQuery (R"( --!syntax_v1
749+ SELECT NodeId, QueryId, Query, CompilationDuration
750+ FROM `/Root/.sys/compile_cache_queries`
751+ ORDER BY NodeId DESC, QueryId DESC
752+ )" , NYdb::NQuery::TTxControl::NoTx ()).GetValueSync ();
753+
754+ UNIT_ASSERT_C (result.IsSuccess (), result.GetIssues ().ToString ());
755+
756+ // Collect all results
757+ struct TCompileCacheKey {
758+ ui32 NodeId;
759+ std::string QueryId;
760+ };
761+ TVector<TCompileCacheKey> compileCacheKeys;
762+ auto resultSet = result.GetResultSet (0 );
763+ NYdb::TResultSetParser parser (resultSet);
764+ while (parser.TryNextRow ()) {
765+ auto nodeId = parser.ColumnParser (" NodeId" ).GetOptionalUint32 ().value ();
766+ auto queryId = parser.ColumnParser (" QueryId" ).GetOptionalUtf8 ().value ();
767+ compileCacheKeys.push_back ({nodeId, queryId});
768+ }
769+
770+ // Verify we got some results
771+ if (compileCacheKeys.size () > 0 ) {
772+ // Verify results are in descending order by NodeId, then QueryId
773+ for (size_t i = 1 ; i < compileCacheKeys.size (); ++i) {
774+ const auto & prev = compileCacheKeys[i - 1 ];
775+ const auto & curr = compileCacheKeys[i];
776+
777+ auto prevKey = std::tie (prev.NodeId , prev.QueryId );
778+ auto currKey = std::tie (curr.NodeId , curr.QueryId );
779+
780+ UNIT_ASSERT_C (prevKey >= currKey,
781+ TStringBuilder () << " Results not in descending order: "
782+ << " compileCacheKeys[" << (i - 1 ) << " ] = (" << prev.NodeId << " , \" " << prev.QueryId << " \" )"
783+ << " < compileCacheKeys[" << i << " ] = (" << curr.NodeId << " , \" " << curr.QueryId << " \" )"
784+ << " . ORDER BY DESC is being ignored by sys view actors." );
785+ }
786+ }
787+ }
788+
789+ Y_UNIT_TEST (TopQueriesOrderByDesc) {
790+ // Test ORDER BY DESC for top_queries sys views
791+ TKikimrRunner kikimr (" " , KikimrDefaultUtDomainRoot, 3 );
792+ auto client = kikimr.GetQueryClient ();
793+ auto session = client.GetSession ().GetValueSync ().GetSession ();
794+
795+ // Execute some queries to populate stats
796+ auto tableClient = kikimr.GetTableClient ();
797+ auto tableSession = tableClient.CreateSession ().GetValueSync ().GetSession ();
798+ {
799+ auto result = tableSession.ExecuteDataQuery (Q_ (R"(
800+ SELECT * FROM `/Root/TwoShard`
801+ )" ), TTxControl::BeginTx ().CommitTx ()).GetValueSync ();
802+ UNIT_ASSERT_VALUES_EQUAL (result.GetStatus (), EStatus::SUCCESS);
803+ }
804+ {
805+ auto result = tableSession.ExecuteDataQuery (Q_ (R"(
806+ SELECT * FROM `/Root/EightShard`
807+ )" ), TTxControl::BeginTx ().CommitTx ()).GetValueSync ();
808+ UNIT_ASSERT_VALUES_EQUAL (result.GetStatus (), EStatus::SUCCESS);
809+ }
810+
811+ // Wait a bit for stats to be collected
812+ ::Sleep (TDuration::MilliSeconds(500 ));
813+
814+ // Test top_queries_by_read_bytes_one_minute
815+ auto result = session.ExecuteQuery (R"( --!syntax_v1
816+ SELECT IntervalEnd, Rank, ReadBytes
817+ FROM `/Root/.sys/top_queries_by_read_bytes_one_minute`
818+ ORDER BY IntervalEnd DESC, Rank DESC
819+ )" , NYdb::NQuery::TTxControl::NoTx ()).GetValueSync ();
820+
821+ UNIT_ASSERT_C (result.IsSuccess (), result.GetIssues ().ToString ());
822+
823+ // Collect all results
824+ TVector<std::pair<TInstant, ui32>> intervalEndRank;
825+ auto resultSet = result.GetResultSet (0 );
826+ NYdb::TResultSetParser parser (resultSet);
827+ while (parser.TryNextRow ()) {
828+ auto intervalEnd = parser.ColumnParser (" IntervalEnd" ).GetOptionalTimestamp ().value ();
829+ auto rank = parser.ColumnParser (" Rank" ).GetOptionalUint32 ().value ();
830+ intervalEndRank.push_back ({intervalEnd, rank});
831+ }
832+
833+ // Verify we got some results (may be empty if no stats collected yet)
834+ if (intervalEndRank.size () > 0 ) {
835+ // Verify results are in descending order by IntervalEnd, then Rank
836+ for (size_t i = 1 ; i < intervalEndRank.size (); ++i) {
837+ const auto & prev = intervalEndRank[i - 1 ];
838+ const auto & curr = intervalEndRank[i];
839+
840+ auto prevKey = std::tie (prev.first , prev.second );
841+ auto currKey = std::tie (curr.first , curr.second );
842+
843+ UNIT_ASSERT_C (prevKey >= currKey,
844+ TStringBuilder () << " Results not in descending order: "
845+ << " intervalEndRank[" << (i - 1 ) << " ] = (" << prev.first .ToString () << " , " << prev.second << " )"
846+ << " < intervalEndRank[" << i << " ] = (" << curr.first .ToString () << " , " << curr.second << " )"
847+ << " . ORDER BY DESC is being ignored by sys view actors." );
848+ }
849+ }
850+ }
851+
567852 Y_UNIT_TEST (QueryStatsSimple) {
568853 auto checkTable = [&] (const TStringBuf tableName) {
569854 TKikimrRunner kikimr (" " , KikimrDefaultUtDomainRoot, 3 );
@@ -920,15 +1205,15 @@ order by SessionId;)", "%Y-%m-%d %H:%M:%S %Z", sessionsSet.front().GetId().data(
9201205 );
9211206 )" ).ExtractValueSync ();
9221207 UNIT_ASSERT_VALUES_EQUAL_C (createResult.GetStatus (), EStatus::SUCCESS, createResult.GetIssues ());
923-
1208+
9241209 auto insertResult = session.ExecuteDataQuery (R"(
9251210 INSERT INTO TestTable (Key, Value) VALUES
9261211 (42, "val"), (NULL, "val1");
9271212 )" , TTxControl::BeginTx ().CommitTx ()).GetValueSync ();
9281213 UNIT_ASSERT_C (insertResult.IsSuccess (), insertResult.GetIssues ().ToString ());
9291214 }
930-
931-
1215+
1216+
9321217 auto session = db.CreateSession ().GetValueSync ().GetSession ();
9331218 TString query = R"(
9341219 SELECT * FROM TestTable WHERE Key in [42, NULL];
0 commit comments