Prefix Lookup
Prefix lookup returns all rows whose primary key starts with a given prefix. It is enabled by choosing a bucket key that is a strict prefix of the primary key — rows sharing the same bucket-key prefix land in the same bucket, so one bucket lookup returns them all.
Table Requirements
- The table must have a primary key.
- The bucket key must be a strict prefix of the primary key (on partitioned tables, of the non-partition portion of the primary key).
- The bucket key cannot equal the full primary key — that is a normal primary-key lookup; use
Lookuperinstead. - The columns passed to
NewPrefixLookup()must equalpartition_keys ++ bucket_key(in that order, if partitioned).
NewPrefixLookup() validates these rules and returns a non-Ok Result (with error_code == ErrorCode::CLIENT_ERROR) describing the violation.
Non-Partitioned Table
Pick a schema where the bucket key is a prefix of the primary key:
auto schema = fluss::Schema::NewBuilder()
.AddColumn("user_id", fluss::DataType::Int())
.AddColumn("session_id", fluss::DataType::String())
.AddColumn("event_seq", fluss::DataType::BigInt())
.AddColumn("event_data", fluss::DataType::String())
.SetPrimaryKeys({"user_id", "session_id", "event_seq"})
.Build();
// Bucket key (user_id, session_id) is a prefix of the primary key.
auto descriptor = fluss::TableDescriptor::NewBuilder()
.SetSchema(schema)
.SetBucketKeys({"user_id", "session_id"})
.SetBucketCount(3)
.Build();
fluss::TablePath table_path("fluss", "sessions");
admin.CreateTable(table_path, descriptor, true);
Create the lookuper with the prefix columns, then call PrefixLookup:
fluss::Table table;
conn.GetTable(table_path, table);
fluss::PrefixLookuper prefix_lookuper;
table.NewPrefixLookup({"user_id", "session_id"}, prefix_lookuper);
auto prefix = table.NewRow();
prefix.Set("user_id", 1);
prefix.Set("session_id", "sess-a");
fluss::PrefixLookupResult result;
prefix_lookuper.PrefixLookup(prefix, result);
for (size_t i = 0; i < result.Size(); ++i) {
auto row = result.GetRow(i);
std::cout << "seq=" << row.GetInt64("event_seq")
<< ", data=" << row.GetString("event_data") << std::endl;
}
Unlike primary-key lookup (which returns a single row via LookupResult::Found()), prefix lookup returns zero or more rows via Size() / GetRow(i), in primary-key order.
Each GetRow(i) is a PrefixRowView with the same getters as a scan RowView: scalars by index or name, and complex (ARRAY / MAP / ROW) columns via GetValue(...) — see Reading Complex Columns.
Partitioned Table
On a partitioned table, the partition columns are stripped from the primary key before the bucket-prefix rule is evaluated. The lookup key must still carry the partition values so the client can route the request to the right partition — so the columns passed to NewPrefixLookup() are partition_keys ++ bucket_key.
auto schema = fluss::Schema::NewBuilder()
.AddColumn("region", fluss::DataType::String())
.AddColumn("user_id", fluss::DataType::Int())
.AddColumn("session_id", fluss::DataType::String())
.AddColumn("event_seq", fluss::DataType::BigInt())
.AddColumn("event_data", fluss::DataType::String())
.SetPrimaryKeys({"region", "user_id", "session_id", "event_seq"})
.Build();
auto descriptor = fluss::TableDescriptor::NewBuilder()
.SetSchema(schema)
.SetPartitionKeys({"region"})
// Bucket key (user_id, session_id) is a prefix of the PK minus the partition column.
.SetBucketKeys({"user_id", "session_id"})
.SetBucketCount(3)
.Build();
fluss::PrefixLookuper prefix_lookuper;
// lookup columns = partition keys ++ bucket key
table.NewPrefixLookup({"region", "user_id", "session_id"}, prefix_lookuper);
auto prefix = table.NewRow();
prefix.Set("region", "US"); // partition column
prefix.Set("user_id", 1);
prefix.Set("session_id", "sess-a");
fluss::PrefixLookupResult result;
prefix_lookuper.PrefixLookup(prefix, result);
for (size_t i = 0; i < result.Size(); ++i) {
auto row = result.GetRow(i);
std::cout << "seq=" << row.GetInt64("event_seq")
<< ", data=" << row.GetString("event_data") << std::endl;
}