mirror of
https://github.com/lancedb/lancedb.git
synced 2026-10-12 14:52:26 +00:00
fix: preserve remote projection column order
This commit is contained in:
Generated
+1
@@ -9524,6 +9524,7 @@ version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"indexmap 2.14.0",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ oauth2 = { version = "5.0", default-features = false }
|
||||
regex = "1.10"
|
||||
semver = "1.0.25"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
serde_json = { version = "1", features = ["preserve_order"] }
|
||||
tempfile = "3.5.0"
|
||||
tokio = { version = "1.23", features = ["rt-multi-thread", "sync"] }
|
||||
tonic = { version = "0.14", features = ["tls-native-roots", "tls-ring"] }
|
||||
|
||||
@@ -1046,6 +1046,8 @@ impl<S: HttpSend> RemoteTable<S> {
|
||||
);
|
||||
}
|
||||
Select::Dynamic(pairs) => {
|
||||
// Keep the caller's alias order in the JSON object sent to the server.
|
||||
// serde_json's preserve_order feature is required here.
|
||||
let alias_map =
|
||||
serde_json::Map::from_iter(pairs.iter().map(|(name, expr)| {
|
||||
(name.clone(), serde_json::Value::String(expr.clone()))
|
||||
@@ -1387,24 +1389,29 @@ impl<S: HttpSend> RemoteTable<S> {
|
||||
});
|
||||
let streams = futures::future::try_join_all(futures);
|
||||
|
||||
if let Some(timeout) = options.timeout {
|
||||
let streams = if let Some(timeout) = options.timeout {
|
||||
let timeout_future = tokio::time::sleep(timeout);
|
||||
tokio::pin!(timeout_future);
|
||||
tokio::pin!(streams);
|
||||
tokio::select! {
|
||||
_ = &mut timeout_future => {
|
||||
Err(Error::Other {
|
||||
return Err(Error::Other {
|
||||
message: format!("Query timeout after {} ms", timeout.as_millis()),
|
||||
source: None,
|
||||
})
|
||||
}
|
||||
result = &mut streams => {
|
||||
Ok(result?)
|
||||
result?
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(streams.await?)
|
||||
}
|
||||
streams.await?
|
||||
};
|
||||
|
||||
Ok(streams
|
||||
.into_iter()
|
||||
.map(|stream| restore_projection_order(stream, &query.base().select))
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn prepare_query_bodies(
|
||||
@@ -1446,6 +1453,52 @@ impl<S: HttpSend> RemoteTable<S> {
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_projection_order(
|
||||
stream: Pin<Box<dyn RecordBatchStream + Send>>,
|
||||
select: &Select,
|
||||
) -> Pin<Box<dyn RecordBatchStream + Send>> {
|
||||
let pairs = match select {
|
||||
Select::Dynamic(pairs) => pairs.iter().map(|(name, _)| name).collect::<Vec<_>>(),
|
||||
Select::Expr(pairs) => pairs.iter().map(|(name, _)| name).collect::<Vec<_>>(),
|
||||
_ => return stream,
|
||||
};
|
||||
|
||||
let schema = stream.schema();
|
||||
let mut remaining = (0..schema.fields().len()).collect::<Vec<_>>();
|
||||
let mut indices = Vec::with_capacity(remaining.len());
|
||||
for name in pairs {
|
||||
if let Some(position) = remaining
|
||||
.iter()
|
||||
.position(|&index| schema.field(index).name() == name)
|
||||
{
|
||||
indices.push(remaining.remove(position));
|
||||
}
|
||||
}
|
||||
indices.extend(remaining);
|
||||
if indices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.all(|(index, &field)| index == field)
|
||||
{
|
||||
return stream;
|
||||
}
|
||||
|
||||
// Older servers may sort alias maps even when the request preserves their
|
||||
// order. Reorder the schema and every batch while keeping server-added fields.
|
||||
let fields = indices
|
||||
.iter()
|
||||
.map(|&index| schema.field(index).clone())
|
||||
.collect::<Vec<_>>();
|
||||
let schema = Arc::new(arrow_schema::Schema::new_with_metadata(
|
||||
fields,
|
||||
schema.metadata().clone(),
|
||||
));
|
||||
let batches = stream.map(move |batch| {
|
||||
batch.and_then(|batch| batch.project(&indices).map_err(DataFusionError::from))
|
||||
});
|
||||
Box::pin(RecordBatchStreamAdapter::new(schema, batches))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct TableDescription {
|
||||
version: u64,
|
||||
@@ -5809,6 +5862,72 @@ mod tests {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::sql(Select::dynamic(&[("p", "x"), ("id", "id")]))]
|
||||
#[case::typed(Select::expr_projection(&[
|
||||
("p", crate::expr::col("x")),
|
||||
("id", crate::expr::col("id")),
|
||||
]))]
|
||||
#[tokio::test]
|
||||
async fn test_query_dynamic_projection_order(#[case] select: Select) {
|
||||
let table = Table::new_with_handler("my_table", |request| {
|
||||
assert_eq!(request.url().path(), "/v1/table/my_table/query/");
|
||||
let body: serde_json::Value =
|
||||
serde_json::from_slice(request.body().unwrap().as_bytes().unwrap()).unwrap();
|
||||
let aliases = body["columns"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.keys()
|
||||
.map(String::as_str)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(aliases, ["p", "id"]);
|
||||
|
||||
// Simulate a server that returns aliases in alphabetical order.
|
||||
let data = RecordBatch::try_new(
|
||||
Arc::new(Schema::new(vec![
|
||||
Field::new("id", DataType::Int32, false),
|
||||
Field::new("p", DataType::Int32, false),
|
||||
])),
|
||||
vec![
|
||||
Arc::new(Int32Array::from(vec![7])),
|
||||
Arc::new(Int32Array::from(vec![42])),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
http::Response::builder()
|
||||
.status(200)
|
||||
.header(CONTENT_TYPE, ARROW_FILE_CONTENT_TYPE)
|
||||
.body(write_ipc_file(&data))
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let batches = table
|
||||
.query()
|
||||
.select(select)
|
||||
.execute()
|
||||
.await
|
||||
.unwrap()
|
||||
.try_collect::<Vec<_>>()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(batches.len(), 1);
|
||||
let schema = batches[0].schema();
|
||||
assert_eq!(
|
||||
schema
|
||||
.fields()
|
||||
.iter()
|
||||
.map(|field| field.name().as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
["p", "id"]
|
||||
);
|
||||
let projected = batches[0]
|
||||
.column(0)
|
||||
.as_any()
|
||||
.downcast_ref::<Int32Array>()
|
||||
.unwrap();
|
||||
assert_eq!(projected.value(0), 42);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_query_vector_nested_field_path() {
|
||||
let expected_data = RecordBatch::try_new(
|
||||
|
||||
Reference in New Issue
Block a user