blob: 2bd583a1003b2c9c8a3f9267f85c6a7ab0de790c [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.
use clap::Parser;
use fluss::client::FlussConnection;
use fluss::config::Config;
use fluss::error::Result;
use fluss::metadata::{DataTypes, Schema, TableDescriptor, TablePath};
use fluss::row::{DataGetters, GenericRow};
#[tokio::main]
#[allow(dead_code)]
pub async fn main() -> Result<()> {
let mut config = Config::parse();
config.bootstrap_servers = "127.0.0.1:9123".to_string();
let conn = FlussConnection::new(config).await?;
// Schema: primary key is (user_id, session_id, event_seq); the bucket key
// (user_id, session_id) is a strict prefix of the primary key, which is
// what enables prefix lookup.
let table_descriptor = TableDescriptor::builder()
.schema(
Schema::builder()
.column("user_id", DataTypes::int())
.column("session_id", DataTypes::string())
.column("event_seq", DataTypes::bigint())
.column("event_data", DataTypes::string())
.primary_key(vec!["user_id", "session_id", "event_seq"])
.build()?,
)
.distributed_by(
Some(3),
vec!["user_id".to_string(), "session_id".to_string()],
)
.build()?;
let table_path = TablePath::new("fluss", "rust_prefix_lookup_example");
let admin = conn.get_admin()?;
admin
.create_table(&table_path, &table_descriptor, true)
.await?;
println!(
"Created KV Table:\n {}\n",
admin.get_table_info(&table_path).await?
);
let table = conn.get_table(&table_path).await?;
let table_upsert = table.new_upsert()?;
let upsert_writer = table_upsert.create_writer()?;
println!("\n=== Upserting session events ===");
for (user_id, session_id, event_seq, event_data) in [
(1, "sess-a", 1i64, "open"),
(1, "sess-a", 2, "click"),
(1, "sess-a", 3, "close"),
(1, "sess-b", 1, "open"),
(2, "sess-c", 1, "open"),
] {
let mut row = GenericRow::new(4);
row.set_field(0, user_id);
row.set_field(1, session_id);
row.set_field(2, event_seq);
row.set_field(3, event_data);
upsert_writer.upsert(&row)?;
println!("Upserted: {row:?}");
}
upsert_writer.flush().await?;
println!("\n=== Prefix lookup by (user_id, session_id) ===");
// `lookup_by` names the prefix columns. The resulting lookuper returns all
// rows whose primary key starts with the given prefix.
let mut prefix_lookuper = table
.new_lookup()?
.lookup_by(vec!["user_id".to_string(), "session_id".to_string()])
.create_lookuper()?;
for (user_id, session_id) in [
(1, "sess-a"),
(1, "sess-b"),
(2, "sess-c"),
(2, "sess-missing"),
] {
let result = prefix_lookuper
.lookup(&make_prefix(user_id, session_id))
.await?;
let rows = result.get_rows()?;
println!(
"user_id={user_id}, session_id={session_id}: {} event(s)",
rows.len()
);
for row in &rows {
println!(" seq={}, data={}", row.get_long(2)?, row.get_string(3)?);
}
}
Ok(())
}
fn make_prefix(user_id: i32, session_id: &str) -> GenericRow<'static> {
let mut row = GenericRow::new(2);
row.set_field(0, user_id);
row.set_field(1, session_id.to_string());
row
}