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.
Lookuper instead.NewPrefixLookup() must equal partition_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.
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.
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; }