Merge "tp: Fix StringStorage sort" into main
diff --git a/src/trace_processor/db/column/string_storage.cc b/src/trace_processor/db/column/string_storage.cc
index 07dfa76..9ab8c1f 100644
--- a/src/trace_processor/db/column/string_storage.cc
+++ b/src/trace_processor/db/column/string_storage.cc
@@ -620,20 +620,51 @@
                                           SortToken* end,
                                           SortDirection direction) const {
   switch (direction) {
-    case SortDirection::kAscending:
+    case SortDirection::kAscending: {
       std::stable_sort(start, end,
-                       [this](const SortToken& a, const SortToken& b) {
-                         return string_pool_->Get((*data_)[a.index]) <
-                                string_pool_->Get((*data_)[b.index]);
+                       [this](const SortToken& lhs, const SortToken& rhs) {
+                         // If RHS is NULL, we know that LHS is not less than
+                         // NULL, as nothing is less then null. This check is
+                         // only required to keep the stability of the sort.
+                         if ((*data_)[rhs.index] == StringPool::Id::Null()) {
+                           return false;
+                         }
+
+                         // If LHS is NULL, it will always be smaller than any
+                         // RHS value.
+                         if ((*data_)[lhs.index] == StringPool::Id::Null()) {
+                           return true;
+                         }
+
+                         // If neither LHS or RHS are NULL, we have to simply
+                         // check which string is smaller.
+                         return string_pool_->Get((*data_)[lhs.index]) <
+                                string_pool_->Get((*data_)[rhs.index]);
                        });
       return;
-    case SortDirection::kDescending:
+    }
+    case SortDirection::kDescending: {
       std::stable_sort(start, end,
-                       [this](const SortToken& a, const SortToken& b) {
-                         return string_pool_->Get((*data_)[a.index]) >
-                                string_pool_->Get((*data_)[b.index]);
+                       [this](const SortToken& lhs, const SortToken& rhs) {
+                         // If LHS is NULL, we know that it's not greater than
+                         // any RHS. This check is only required to keep the
+                         // stability of the sort.
+                         if ((*data_)[lhs.index] == StringPool::Id::Null()) {
+                           return false;
+                         }
+
+                         // If RHS is NULL, everything will be greater from it.
+                         if ((*data_)[rhs.index] == StringPool::Id::Null()) {
+                           return true;
+                         }
+
+                         // If neither LHS or RHS are NULL, we have to simply
+                         // check which string is smaller.
+                         return string_pool_->Get((*data_)[lhs.index]) >
+                                string_pool_->Get((*data_)[rhs.index]);
                        });
       return;
+    }
   }
   PERFETTO_FATAL("For GCC");
 }
diff --git a/test/trace_processor/diff_tests/syntax/filtering_tests.py b/test/trace_processor/diff_tests/syntax/filtering_tests.py
index adf8c3e..22b4111 100644
--- a/test/trace_processor/diff_tests/syntax/filtering_tests.py
+++ b/test/trace_processor/diff_tests/syntax/filtering_tests.py
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from python.generators.diff_tests.testing import DataPath
+from python.generators.diff_tests.testing import DataPath, TextProto
 from python.generators.diff_tests.testing import Csv
 from python.generators.diff_tests.testing import DiffTestBlueprint
 from python.generators.diff_tests.testing import TestSuite
@@ -189,3 +189,49 @@
         "cnt"
         0
         """))
+
+  def test_string_null_vs_empty(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        CREATE PERFETTO TABLE foo AS
+        SELECT 0 as id, NULL AS strings
+        UNION ALL
+        SELECT 1, 'cheese'
+        UNION ALL
+        SELECT 2, NULL
+        UNION ALL
+        SELECT 3, '';
+
+        SELECT * FROM foo ORDER BY strings ASC;
+        """,
+        out=Csv("""
+        "id","strings"
+        0,"[NULL]"
+        2,"[NULL]"
+        3,""
+        1,"cheese"
+        """))
+
+  def test_string_null_vs_empty_desc(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        CREATE PERFETTO TABLE foo AS
+        SELECT 0 as id, NULL AS strings
+        UNION ALL
+        SELECT 1, 'cheese'
+        UNION ALL
+        SELECT 2, NULL
+        UNION ALL
+        SELECT 3, '';
+
+        SELECT * FROM foo ORDER BY strings DESC;
+        """,
+        out=Csv("""
+        "id","strings"
+        1,"cheese"
+        3,""
+        0,"[NULL]"
+        2,"[NULL]"
+        """))