trace_processor: Make GetColumnsForTable report errors

GetColumnsForTable now returns util::Status and so can report validation
errors rather than calling PERFETTO_FAIILURE.

Bug: 163136067
Change-Id: Icc01332aa7be56389c3a1250455ced2fa659121d
diff --git a/Android.bp b/Android.bp
index 37c42cf..8db7f44 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6842,6 +6842,7 @@
     "src/trace_processor/sqlite/query_constraints_unittest.cc",
     "src/trace_processor/sqlite/span_join_operator_table_unittest.cc",
     "src/trace_processor/sqlite/sqlite3_str_split_unittest.cc",
+    "src/trace_processor/sqlite/sqlite_utils_unittest.cc",
   ],
 }
 
diff --git a/src/trace_processor/sqlite/BUILD.gn b/src/trace_processor/sqlite/BUILD.gn
index ba2c13f..52352b7 100644
--- a/src/trace_processor/sqlite/BUILD.gn
+++ b/src/trace_processor/sqlite/BUILD.gn
@@ -61,6 +61,7 @@
       "query_constraints_unittest.cc",
       "span_join_operator_table_unittest.cc",
       "sqlite3_str_split_unittest.cc",
+      "sqlite_utils_unittest.cc",
     ]
     deps = [
       ":sqlite",
diff --git a/src/trace_processor/sqlite/span_join_operator_table.cc b/src/trace_processor/sqlite/span_join_operator_table.cc
index 1357ddf..c847a25 100644
--- a/src/trace_processor/sqlite/span_join_operator_table.cc
+++ b/src/trace_processor/sqlite/span_join_operator_table.cc
@@ -311,7 +311,11 @@
         desc.name.c_str());
   }
 
-  auto cols = sqlite_utils::GetColumnsForTable(db_, desc.name);
+  std::vector<SqliteTable::Column> cols;
+  auto status = sqlite_utils::GetColumnsForTable(db_, desc.name, cols);
+  if (!status.ok()) {
+    return status;
+  }
 
   uint32_t required_columns_found = 0;
   uint32_t ts_idx = std::numeric_limits<uint32_t>::max();
diff --git a/src/trace_processor/sqlite/sqlite_utils.h b/src/trace_processor/sqlite/sqlite_utils.h
index 4896df7..cc5c459 100644
--- a/src/trace_processor/sqlite/sqlite_utils.h
+++ b/src/trace_processor/sqlite/sqlite_utils.h
@@ -26,6 +26,7 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/string_utils.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sqlite_table.h"
 
@@ -380,9 +381,11 @@
   sqlite3_result_double(ctx, value);
 }
 
-inline std::vector<SqliteTable::Column> GetColumnsForTable(
+inline util::Status GetColumnsForTable(
     sqlite3* db,
-    const std::string& raw_table_name) {
+    const std::string& raw_table_name,
+    std::vector<SqliteTable::Column>& columns) {
+  PERFETTO_DCHECK(columns.empty());
   char sql[1024];
   const char kRawSql[] = "SELECT name, type from pragma_table_info(\"%s\")";
 
@@ -394,21 +397,18 @@
   sqlite3_stmt* raw_stmt = nullptr;
   int err = sqlite3_prepare_v2(db, sql, n, &raw_stmt, nullptr);
   if (err != SQLITE_OK) {
-    PERFETTO_ELOG("Preparing database failed");
-    return {};
+    return util::ErrStatus("Preparing database failed");
   }
   ScopedStmt stmt(raw_stmt);
   PERFETTO_DCHECK(sqlite3_column_count(*stmt) == 2);
 
-  std::vector<SqliteTable::Column> columns;
   for (;;) {
     err = sqlite3_step(raw_stmt);
     if (err == SQLITE_DONE)
       break;
     if (err != SQLITE_ROW) {
-      PERFETTO_ELOG("Querying schema of table %s failed",
-                    raw_table_name.c_str());
-      return {};
+      return util::ErrStatus("Querying schema of table %s failed",
+                             raw_table_name.c_str());
     }
 
     const char* name =
@@ -416,31 +416,31 @@
     const char* raw_type =
         reinterpret_cast<const char*>(sqlite3_column_text(*stmt, 1));
     if (!name || !raw_type || !*name) {
-      PERFETTO_FATAL("Schema for %s has invalid column values",
-                     raw_table_name.c_str());
+      return util::ErrStatus("Schema for %s has invalid column values",
+                             raw_table_name.c_str());
     }
 
     SqlValue::Type type;
-    if (strcmp(raw_type, "STRING") == 0) {
+    if (base::CaseInsensitiveEqual(raw_type, "STRING")) {
       type = SqlValue::Type::kString;
-    } else if (strcmp(raw_type, "DOUBLE") == 0) {
+    } else if (base::CaseInsensitiveEqual(raw_type, "DOUBLE")) {
       type = SqlValue::Type::kDouble;
-    } else if (strcmp(raw_type, "BIG INT") == 0 ||
-               strcmp(raw_type, "UNSIGNED INT") == 0 ||
-               strcmp(raw_type, "INT") == 0 ||
-               strcmp(raw_type, "BOOLEAN") == 0) {
+    } else if (base::CaseInsensitiveEqual(raw_type, "BIG INT") ||
+               base::CaseInsensitiveEqual(raw_type, "UNSIGNED INT") ||
+               base::CaseInsensitiveEqual(raw_type, "INT") ||
+               base::CaseInsensitiveEqual(raw_type, "BOOLEAN")) {
       type = SqlValue::Type::kLong;
     } else if (!*raw_type) {
       PERFETTO_DLOG("Unknown column type for %s %s", raw_table_name.c_str(),
                     name);
       type = SqlValue::Type::kNull;
     } else {
-      PERFETTO_FATAL("Unknown column type '%s' on table %s", raw_type,
-                     raw_table_name.c_str());
+      return util::ErrStatus("Unknown column type '%s' on table %s", raw_type,
+                             raw_table_name.c_str());
     }
     columns.emplace_back(columns.size(), name, type);
   }
-  return columns;
+  return util::OkStatus();
 }
 
 template <typename T>
diff --git a/src/trace_processor/sqlite/sqlite_utils_unittest.cc b/src/trace_processor/sqlite/sqlite_utils_unittest.cc
new file mode 100644
index 0000000..1d1de13
--- /dev/null
+++ b/src/trace_processor/sqlite/sqlite_utils_unittest.cc
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+class GetColumnsForTableTest : public ::testing::Test {
+ public:
+  GetColumnsForTableTest() {
+    sqlite3* db = nullptr;
+    PERFETTO_CHECK(sqlite3_initialize() == SQLITE_OK);
+    PERFETTO_CHECK(sqlite3_open(":memory:", &db) == SQLITE_OK);
+    db_.reset(db);
+  }
+
+  void PrepareValidStatement(const std::string& sql) {
+    int size = static_cast<int>(sql.size());
+    sqlite3_stmt* stmt;
+    ASSERT_EQ(sqlite3_prepare_v2(*db_, sql.c_str(), size, &stmt, nullptr),
+              SQLITE_OK);
+    stmt_.reset(stmt);
+  }
+
+  void RunStatement(const std::string& sql) {
+    PrepareValidStatement(sql);
+    ASSERT_EQ(sqlite3_step(stmt_.get()), SQLITE_DONE);
+  }
+
+ protected:
+  ScopedDb db_;
+  ScopedStmt stmt_;
+};
+
+TEST_F(GetColumnsForTableTest, ValidInput) {
+  RunStatement("CREATE TABLE foo (name STRING, ts INT, dur INT);");
+  std::vector<SqliteTable::Column> columns;
+  auto status = sqlite_utils::GetColumnsForTable(*db_, "foo", columns);
+  ASSERT_TRUE(status.ok());
+}
+
+TEST_F(GetColumnsForTableTest, UnknownType) {
+  // Currently GetColumnsForTable does not work with tables containing types it
+  // doesn't recognise. This just ensures that the query fails rather than
+  // crashing.
+  RunStatement("CREATE TABLE foo (name NUM, ts INT, dur INT);");
+  std::vector<SqliteTable::Column> columns;
+  auto status = sqlite_utils::GetColumnsForTable(*db_, "foo", columns);
+  ASSERT_FALSE(status.ok());
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto