use crate::client::ClientSchemaGetter;
use crate::error::{Error, Result};
use crate::metadata::{RowType, Schema, index_mapping};
use crate::record::{ReadContext, to_arrow_schema};
use arrow_schema::SchemaRef;
use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::Arc;
pub(crate) struct ReadContextResolver {
initial_schema_id: i16,
contexts: RwLock<HashMap<i16, ResolvedContexts>>,
projected_fields: Option<Vec<usize>>,
schema_getter: Option<Arc<ClientSchemaGetter>>,
fixed_target: Option<FixedSchemaTarget>,
}
struct ResolvedContexts {
local: Arc<ReadContext>,
remote: Arc<ReadContext>,
}
struct FixedSchemaTarget {
fluss_schema: Schema,
arrow_schema: SchemaRef,
row_type: Arc<RowType>,
}
impl ReadContextResolver {
pub fn new(
initial_schema_id: i16,
local_context: Arc<ReadContext>,
remote_context: Arc<ReadContext>,
projected_fields: Option<Vec<usize>>,
) -> Self {
let mut map = HashMap::new();
map.insert(
initial_schema_id,
ResolvedContexts {
local: local_context,
remote: remote_context,
},
);
Self {
initial_schema_id,
contexts: RwLock::new(map),
projected_fields,
schema_getter: None,
fixed_target: None,
}
}
pub fn with_schema_getter(mut self, schema_getter: Arc<ClientSchemaGetter>) -> Self {
self.schema_getter = Some(schema_getter);
self
}
pub fn with_fixed_schema(mut self, target_fluss_schema: &Schema) -> Self {
if self.projected_fields.is_some() {
return self;
}
self.fixed_target = {
let guard = self.contexts.read();
guard
.get(&self.initial_schema_id)
.map(|ctx| FixedSchemaTarget {
fluss_schema: target_fluss_schema.clone(),
arrow_schema: ctx.local.target_schema(),
row_type: ctx.local.row_type_arc(),
})
};
self
}
pub fn resolve(&self, schema_id: i16, is_remote: bool) -> Option<Arc<ReadContext>> {
let effective_id = if self.projected_fields.is_some() {
self.initial_schema_id
} else {
schema_id
};
let guard = self.contexts.read();
guard.get(&effective_id).map(|ctx| {
if is_remote {
Arc::clone(&ctx.remote)
} else {
Arc::clone(&ctx.local)
}
})
}
pub async fn fetch_and_register(&self, schema_id: i16) -> Result<()> {
if self.projected_fields.is_some() || self.contexts.read().contains_key(&schema_id) {
return Ok(());
}
let schema_getter = self
.schema_getter
.as_ref()
.ok_or_else(|| Error::UnexpectedError {
message: format!("No schema getter configured for schema_id {schema_id}"),
source: None,
})?
.clone();
let schema = schema_getter.get_schema(schema_id as i32).await?;
self.register_schema(schema_id, &schema)?;
Ok(())
}
pub fn register_schema(&self, schema_id: i16, schema: &Schema) -> Result<()> {
if self.projected_fields.is_some() {
return Ok(());
}
if self.contexts.read().contains_key(&schema_id) {
return Ok(());
}
let source_row_type = schema.row_type();
let source_arrow_schema = to_arrow_schema(source_row_type)?;
let source_row_type_arc = Arc::new(source_row_type.clone());
let fixed_target = self
.fixed_target
.as_ref()
.map(|target| {
index_mapping(schema, &target.fluss_schema).map(|mapping| {
(
target.arrow_schema.clone(),
target.row_type.clone(),
Arc::<[i32]>::from(mapping.into_boxed_slice()),
)
})
})
.transpose()?;
let output_row_type = fixed_target
.as_ref()
.map(|(_, row_type, _)| row_type.clone())
.unwrap_or(source_row_type_arc);
let mut local_context =
ReadContext::new(source_arrow_schema.clone(), output_row_type.clone(), false)
.with_fluss_row_type(output_row_type.clone());
let mut remote_context =
ReadContext::new(source_arrow_schema, output_row_type.clone(), true)
.with_fluss_row_type(output_row_type);
if let Some((target_schema, _, schema_alignment)) = fixed_target {
local_context = local_context
.with_target_schema_alignment(target_schema.clone(), Arc::clone(&schema_alignment));
remote_context =
remote_context.with_target_schema_alignment(target_schema, schema_alignment);
}
let local_context = Arc::new(local_context);
let remote_context = Arc::new(remote_context);
self.contexts.write().insert(
schema_id,
ResolvedContexts {
local: local_context,
remote: remote_context,
},
);
Ok(())
}
pub fn initial_schema_id(&self) -> i16 {
self.initial_schema_id
}
#[allow(dead_code)]
pub fn projected_fields(&self) -> Option<&[usize]> {
self.projected_fields.as_deref()
}
}
#[cfg(test)]
mod tests {
use super::ReadContextResolver;
use crate::error::Result;
use crate::metadata::{DataTypes, Schema};
use crate::record::{ReadContext, to_arrow_schema};
use std::sync::Arc;
#[test]
fn fixed_schema_does_not_capture_target_with_projection() -> Result<()> {
let schema = Schema::builder()
.column("id", DataTypes::int())
.column("name", DataTypes::string())
.build()?;
let arrow_schema = to_arrow_schema(schema.row_type())?;
let projected_fields = vec![0];
let projected_row_type = Arc::new(schema.row_type().project(&projected_fields)?);
let local_context = Arc::new(ReadContext::with_projection_pushdown(
arrow_schema.clone(),
Arc::clone(&projected_row_type),
projected_fields.clone(),
false,
)?);
let remote_context = Arc::new(ReadContext::with_projection_pushdown(
arrow_schema,
projected_row_type,
projected_fields.clone(),
true,
)?);
let resolver =
ReadContextResolver::new(1, local_context, remote_context, Some(projected_fields))
.with_fixed_schema(&schema);
assert!(resolver.fixed_target.is_none());
Ok(())
}
}