tp: introduce virtual table module for table pointers
This CL implements a virtual table module which takes a Perfetto table
pointer and exposes its contents to SQLite.
This is similar in spirit to DbSqliteTable but with the pointer being
taken as an input argument rather than statically defined in C++.
Change-Id: I00473ed909a47790714379ea2795d4f088327a06
diff --git a/Android.bp b/Android.bp
index 05b6bfb..7fc579a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12160,6 +12160,7 @@
"src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc",
"src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.cc",
"src/trace_processor/perfetto_sql/engine/runtime_table_function.cc",
+ "src/trace_processor/perfetto_sql/engine/table_pointer_module.cc",
],
}
diff --git a/BUILD b/BUILD
index 6f1ab75..1a4eb3d 100644
--- a/BUILD
+++ b/BUILD
@@ -2233,6 +2233,8 @@
"src/trace_processor/perfetto_sql/engine/perfetto_sql_preprocessor.h",
"src/trace_processor/perfetto_sql/engine/runtime_table_function.cc",
"src/trace_processor/perfetto_sql/engine/runtime_table_function.h",
+ "src/trace_processor/perfetto_sql/engine/table_pointer_module.cc",
+ "src/trace_processor/perfetto_sql/engine/table_pointer_module.h",
],
)
diff --git a/src/trace_processor/perfetto_sql/engine/BUILD.gn b/src/trace_processor/perfetto_sql/engine/BUILD.gn
index 6a3baf1..e297c2b 100644
--- a/src/trace_processor/perfetto_sql/engine/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/engine/BUILD.gn
@@ -30,6 +30,8 @@
"perfetto_sql_preprocessor.h",
"runtime_table_function.cc",
"runtime_table_function.h",
+ "table_pointer_module.cc",
+ "table_pointer_module.h",
]
deps = [
"../..:metatrace",
diff --git a/src/trace_processor/perfetto_sql/engine/table_pointer_module.cc b/src/trace_processor/perfetto_sql/engine/table_pointer_module.cc
new file mode 100644
index 0000000..3a8d6ee
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/engine/table_pointer_module.cc
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2024 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/perfetto_sql/engine/table_pointer_module.h"
+
+#include <sqlite3.h>
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/public/compiler.h"
+#include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/table.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+
+namespace perfetto::trace_processor {
+
+int TablePointerModule::Connect(sqlite3* db,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab** vtab,
+ char**) {
+ static constexpr char kSchema[] = R"(
+ CREATE TABLE x(
+ c0 ANY,
+ c1 ANY,
+ c2 ANY,
+ c3 ANY,
+ c4 ANY,
+ c5 ANY,
+ c6 ANY,
+ c7 ANY,
+ c8 ANY,
+ c9 ANY,
+ c10 ANY,
+ c11 ANY,
+ c12 ANY,
+ c13 ANY,
+ c14 ANY,
+ c15 ANY,
+ tab BLOB HIDDEN,
+ row INTEGER HIDDEN,
+ PRIMARY KEY(row)
+ ) WITHOUT ROWID
+ )";
+ if (int ret = sqlite3_declare_vtab(db, kSchema); ret != SQLITE_OK) {
+ return ret;
+ }
+ std::unique_ptr<Vtab> res = std::make_unique<Vtab>();
+ *vtab = res.release();
+ return SQLITE_OK;
+}
+
+int TablePointerModule::Disconnect(sqlite3_vtab* vtab) {
+ delete GetVtab(vtab);
+ return SQLITE_OK;
+}
+
+int TablePointerModule::BestIndex(sqlite3_vtab* tab, sqlite3_index_info* info) {
+ std::array<bool, kBindableColumnCount> bound_cols{};
+ uint32_t bound_cols_count = 0;
+ bool seen_tab_eq = false;
+ for (int i = 0; i < info->nConstraint; ++i) {
+ auto& in = info->aConstraint[i];
+ auto& out = info->aConstraintUsage[i];
+ // Ignore any unusable constraints.
+ if (!in.usable) {
+ continue;
+ }
+ // Disallow row constraints.
+ if (in.iColumn == kRowColumnIndex) {
+ return sqlite::utils::SetError(tab, "Constraint on row not allowed");
+ }
+ // Bind constraints.
+ if (in.op == kBindConstraint) {
+ if (in.iColumn >= kBindableColumnCount) {
+ return sqlite::utils::SetError(tab, "Invalid bound column");
+ }
+ bool& bound = bound_cols[static_cast<uint32_t>(in.iColumn)];
+ if (bound) {
+ return sqlite::utils::SetError(tab, "Duplicate bound column");
+ }
+ // TODO(lalitm): all of the values here should be constants.
+ out.argvIndex = kBoundColumnArgvOffset + in.iColumn;
+ out.omit = true;
+ bound = true;
+ bound_cols_count++;
+ continue;
+ }
+ // Constraint on tab.
+ if (in.iColumn == kTableColumnIndex) {
+ if (in.op != SQLITE_INDEX_CONSTRAINT_EQ) {
+ return sqlite::utils::SetError(
+ tab, "tab only supports equality constraints");
+ }
+ out.argvIndex = kTableArgvIndex;
+ out.omit = true;
+ seen_tab_eq = true;
+ continue;
+ }
+ // Any other constraints on the columns.
+ // TODO(lalitm): implement support for passing these down.
+ }
+ if (!seen_tab_eq) {
+ return sqlite::utils::SetError(tab, "table must be bound");
+ }
+ if (bound_cols_count == 0) {
+ return sqlite::utils::SetError(tab, "At least one column must be bound");
+ }
+ for (uint32_t i = 0; i < bound_cols_count; ++i) {
+ if (!bound_cols[i]) {
+ return sqlite::utils::SetError(tab, "Bound columns are not dense");
+ }
+ }
+ return SQLITE_OK;
+}
+
+int TablePointerModule::Open(sqlite3_vtab*, sqlite3_vtab_cursor** cursor) {
+ std::unique_ptr<Cursor> c = std::make_unique<Cursor>();
+ *cursor = c.release();
+ return SQLITE_OK;
+}
+
+int TablePointerModule::Close(sqlite3_vtab_cursor* cursor) {
+ delete GetCursor(cursor);
+ return SQLITE_OK;
+}
+
+int TablePointerModule::Filter(sqlite3_vtab_cursor* cur,
+ int,
+ const char*,
+ int argc,
+ sqlite3_value** argv) {
+ auto* c = GetCursor(cur);
+ if (argc == 0) {
+ return sqlite::utils::SetError(c->pVtab, "tab parameter is not set");
+ }
+ c->table = static_cast<const Table*>(sqlite3_value_pointer(argv[0], "TABLE"));
+ if (!c->table) {
+ return sqlite::utils::SetError(c->pVtab, "tab parameter is NULL");
+ }
+ c->col_count = 0;
+ for (int i = 1; i < argc; ++i) {
+ if (sqlite3_value_type(argv[i]) != SQLITE_TEXT) {
+ return sqlite::utils::SetError(c->pVtab, "Column name is not text");
+ }
+
+ std::string_view tok(
+ reinterpret_cast<const char*>(sqlite3_value_text(argv[i])));
+ auto it = std::find_if(
+ c->table->columns().begin(), c->table->columns().end(),
+ [&tok](const ColumnLegacy& col) { return col.name() == tok; });
+ if (it == c->table->columns().end()) {
+ return sqlite::utils::SetError(c->pVtab,
+ "column does not exist in table");
+ }
+ c->bound_col_to_table_index[c->col_count++] =
+ static_cast<uint32_t>(std::distance(c->table->columns().begin(), it));
+ }
+ c->iterator = c->table->IterateRows();
+ return SQLITE_OK;
+}
+
+int TablePointerModule::Next(sqlite3_vtab_cursor* cur) {
+ auto* c = GetCursor(cur);
+ ++(*c->iterator);
+ return SQLITE_OK;
+}
+
+int TablePointerModule::Eof(sqlite3_vtab_cursor* cur) {
+ return !*GetCursor(cur)->iterator;
+}
+
+int TablePointerModule::Column(sqlite3_vtab_cursor* cur,
+ sqlite3_context* ctx,
+ int raw_n) {
+ auto* c = GetCursor(cur);
+ auto N = static_cast<uint32_t>(raw_n);
+ if (PERFETTO_UNLIKELY(N >= c->col_count)) {
+ return sqlite::utils::SetError(c->pVtab,
+ "Asking for value of non bound column");
+ }
+ uint32_t table_index = c->bound_col_to_table_index[N];
+ sqlite::utils::ReportSqlValue(ctx, c->iterator->Get(table_index));
+ return SQLITE_OK;
+}
+
+int TablePointerModule::Rowid(sqlite3_vtab_cursor*, sqlite_int64*) {
+ return SQLITE_ERROR;
+}
+
+int TablePointerModule::FindFunction(sqlite3_vtab*,
+ int,
+ const char* name,
+ FindFunctionFn** fn,
+ void**) {
+ if (base::CaseInsensitiveEqual(name, "__intrinsic_table_ptr_bind")) {
+ *fn = [](sqlite3_context* ctx, int, sqlite3_value**) {
+ return sqlite::result::Error(ctx, "Should not be called.");
+ };
+ return SQLITE_INDEX_CONSTRAINT_FUNCTION + 1;
+ }
+ return SQLITE_OK;
+}
+
+} // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/engine/table_pointer_module.h b/src/trace_processor/perfetto_sql/engine/table_pointer_module.h
new file mode 100644
index 0000000..fe8f984
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/engine/table_pointer_module.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_TABLE_POINTER_MODULE_H_
+#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_TABLE_POINTER_MODULE_H_
+
+#include <array>
+#include <cstdint>
+#include <optional>
+
+#include "src/trace_processor/db/table.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_module.h"
+
+namespace perfetto::trace_processor {
+
+// SQLite module which allows iteration over a table pointer (i.e. a instance of
+// Table which is being directly passed in as a SQL value).
+//
+// Note: this class is *not* intended to be used directly by end users. It is
+// a building block intended for use by very low-level macros in the standard
+// library.
+struct TablePointerModule : sqlite::Module<TablePointerModule> {
+ static constexpr int kBindConstraint = SQLITE_INDEX_CONSTRAINT_FUNCTION + 1;
+ static constexpr int kBindableColumnCount = 16;
+ static constexpr int kTableColumnIndex = kBindableColumnCount;
+ static constexpr int kRowColumnIndex = kBindableColumnCount + 1;
+ static constexpr int kTableArgvIndex = 1;
+ static constexpr int kBoundColumnArgvOffset = 2;
+
+ using Context = void;
+ struct Vtab : sqlite::Module<TablePointerModule>::Vtab {};
+ struct Cursor : sqlite::Module<TablePointerModule>::Cursor {
+ const Table* table = nullptr;
+ std::array<uint32_t, kBindableColumnCount> bound_col_to_table_index{};
+ uint32_t col_count = 0;
+ std::optional<Table::Iterator> iterator;
+ };
+
+ static constexpr auto kType = kEponymousOnly;
+ static constexpr bool kSupportsWrites = false;
+
+ static int Connect(sqlite3*,
+ void*,
+ int,
+ const char* const*,
+ sqlite3_vtab**,
+ char**);
+ static int Disconnect(sqlite3_vtab*);
+
+ static int BestIndex(sqlite3_vtab*, sqlite3_index_info*);
+
+ static int Open(sqlite3_vtab*, sqlite3_vtab_cursor**);
+ static int Close(sqlite3_vtab_cursor*);
+
+ static int Filter(sqlite3_vtab_cursor*,
+ int,
+ const char*,
+ int,
+ sqlite3_value**);
+ static int Next(sqlite3_vtab_cursor*);
+ static int Eof(sqlite3_vtab_cursor*);
+ static int Column(sqlite3_vtab_cursor*, sqlite3_context*, int);
+ static int Rowid(sqlite3_vtab_cursor*, sqlite_int64*);
+
+ static int FindFunction(sqlite3_vtab*,
+ int,
+ const char*,
+ FindFunctionFn**,
+ void**);
+};
+
+} // namespace perfetto::trace_processor
+
+#endif // SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_TABLE_POINTER_MODULE_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/utils.h b/src/trace_processor/perfetto_sql/intrinsics/functions/utils.h
index cc502ff..e3d1263 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/utils.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/utils.h
@@ -347,6 +347,18 @@
}
};
+struct TablePtrBind : public SqlFunction {
+ static base::Status Run(void*,
+ size_t,
+ sqlite3_value**,
+ SqlValue&,
+ Destructors&) {
+ return base::ErrStatus(
+ "__intrinsic_table_ptr_bind should not be called from the global "
+ "scope");
+ }
+};
+
struct Glob : public SqlFunction {
static base::Status Run(void*,
size_t,
diff --git a/src/trace_processor/sqlite/bindings/sqlite_module.h b/src/trace_processor/sqlite/bindings/sqlite_module.h
index 0da20cc..61bcb7b 100644
--- a/src/trace_processor/sqlite/bindings/sqlite_module.h
+++ b/src/trace_processor/sqlite/bindings/sqlite_module.h
@@ -207,10 +207,11 @@
// Implementations MUST define this function themselves if
// |kDoesOverloadFunctions| == |true|; this function is declared but *not*
// defined so linker errors will be thrown if not defined.
+ using FindFunctionFn = void(sqlite3_context*, int, sqlite3_value**);
static int FindFunction(sqlite3_vtab*,
int,
const char*,
- void (**)(sqlite3_context*, int, sqlite3_value**),
+ FindFunctionFn**,
void**);
// Helper function to cast the module context pointer to the correct type.
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 1b3dea3..a99bf77 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -43,7 +43,6 @@
#include "perfetto/trace_processor/iterator.h"
#include "perfetto/trace_processor/trace_blob_view.h"
#include "perfetto/trace_processor/trace_processor.h"
-#include "sqlite/sqlite_utils.h"
#include "src/trace_processor/importers/android_bugreport/android_bugreport_parser.h"
#include "src/trace_processor/importers/common/clock_tracker.h"
#include "src/trace_processor/importers/common/metadata_tracker.h"
@@ -66,6 +65,7 @@
#include "src/trace_processor/metrics/metrics.h"
#include "src/trace_processor/metrics/sql/amalgamated_sql_metrics.h"
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h"
+#include "src/trace_processor/perfetto_sql/engine/table_pointer_module.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/base64.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/clock_functions.h"
#include "src/trace_processor/perfetto_sql/intrinsics/functions/create_function.h"
@@ -667,6 +667,8 @@
RegisterFunction<Base64Encode>(engine_.get(), "BASE64_ENCODE", 1);
RegisterFunction<Demangle>(engine_.get(), "DEMANGLE", 1);
RegisterFunction<SourceGeq>(engine_.get(), "SOURCE_GEQ", -1);
+ RegisterFunction<TablePtrBind>(engine_.get(), "__intrinsic_table_ptr_bind",
+ -1);
RegisterFunction<ExportJson>(engine_.get(), "EXPORT_JSON", 1,
context_.storage.get(), false);
RegisterFunction<ExtractArg>(engine_.get(), "EXTRACT_ARG", 2,
@@ -780,6 +782,8 @@
"sqlstats", storage);
engine_->sqlite_engine()->RegisterVirtualTableModule<StatsModule>("stats",
storage);
+ engine_->sqlite_engine()->RegisterVirtualTableModule<TablePointerModule>(
+ "__intrinsic_table_ptr", nullptr);
// New style db-backed tables.
// Note: if adding a table here which might potentially contain many rows