Skip to content

Commit 3b634aa

Browse files
gstvgrluvaton
andauthored
Add support for lambda column capture (apache#21323)
## Which issue does this PR close? Part of apache#21172 ## Rationale for this change Capture support wasn't implemented in the core lambda support to reduce PR size and because it requires further discussions not tied to basic support ## What changes are included in this PR? Lambda capture list_values_row_number helper to adjust a list to the lambda scope Make apache#18329 lambda-aware ## Are these changes tested? sqllogictests for lambda capture and CaseWhen unit tests for list_values_row_number ## Are there any user-facing changes? This add breaking changes to unreleased items only --------- Co-authored-by: Raz Luvaton <16746759+rluvaton@users.noreply.github.com>
1 parent 0c38ebb commit 3b634aa

12 files changed

Lines changed: 661 additions & 83 deletions

File tree

datafusion/common/src/utils/mod.rs

Lines changed: 155 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,23 @@ use arrow::array::{
3131
cast::AsArray,
3232
};
3333
use arrow::array::{
34-
Datum, GenericListArray, Int32Array, Int64Array, MutableArrayData, make_array,
34+
ArrowPrimitiveType, Datum, GenericListArray, Int32Array, Int64Array,
35+
MutableArrayData, PrimitiveArray, make_array,
3536
};
3637
use arrow::array::{LargeListViewArray, ListViewArray};
3738
use arrow::buffer::{OffsetBuffer, ScalarBuffer};
3839
use arrow::compute::kernels::cmp::neq;
3940
use arrow::compute::kernels::length::length;
4041
use arrow::compute::{SortColumn, SortOptions, partition};
41-
use arrow::datatypes::{DataType, Field, SchemaRef};
42+
use arrow::datatypes::{
43+
ArrowNativeType, DataType, Field, Int32Type, Int64Type, SchemaRef,
44+
};
4245
#[cfg(feature = "sql")]
4346
use sqlparser::{ast::Ident, dialect::GenericDialect, parser::Parser};
4447
use std::borrow::{Borrow, Cow};
4548
use std::cmp::{Ordering, min};
4649
use std::collections::HashSet;
50+
use std::iter::repeat_n;
4751
use std::num::NonZero;
4852
use std::ops::Range;
4953
use std::sync::{Arc, LazyLock};
@@ -1181,6 +1185,74 @@ fn truncate_list_nulls<O: OffsetSizeTrait>(
11811185
Ok(list.clone())
11821186
}
11831187

1188+
/// If `array` is a list or a map, returns a new array of the same length as it's inner values
1189+
/// where each value is the 1-based index of the sublist it's contained. Example:
1190+
///
1191+
/// `[[1], [2, 3], [4, 5, 6]] => [1, 2, 2, 3, 3, 3]`
1192+
///
1193+
/// Otherwise returns an error
1194+
pub fn list_values_row_number(array: &dyn Array) -> Result<ArrayRef> {
1195+
match array.data_type() {
1196+
DataType::List(_) => Ok(Arc::new(variable_size_list_values_row_number::<
1197+
Int32Type,
1198+
>(array.as_list().offsets()))),
1199+
DataType::LargeList(_) => Ok(Arc::new(variable_size_list_values_row_number::<
1200+
Int64Type,
1201+
>(array.as_list().offsets()))),
1202+
DataType::ListView(_) => Ok(Arc::new(variable_size_list_values_row_number::<
1203+
Int32Type,
1204+
>(array.as_list_view().offsets()))),
1205+
DataType::LargeListView(_) => {
1206+
Ok(Arc::new(variable_size_list_values_row_number::<Int64Type>(
1207+
array.as_list_view().offsets(),
1208+
)))
1209+
}
1210+
DataType::FixedSizeList(_, _) => {
1211+
let fixed_size_list = array.as_fixed_size_list();
1212+
1213+
Ok(Arc::new(fsl_values_row_number(
1214+
fixed_size_list.value_length(),
1215+
fixed_size_list.len(),
1216+
)?))
1217+
}
1218+
DataType::Map(_, _) => Ok(Arc::new(variable_size_list_values_row_number::<
1219+
Int32Type,
1220+
>(array.as_map().offsets()))),
1221+
other => _exec_err!("expected list, got {other}"),
1222+
}
1223+
}
1224+
1225+
/// [0, 2, 2, 5, 6] -> [0, 0, 2, 2, 2, 3]
1226+
fn variable_size_list_values_row_number<T: ArrowPrimitiveType>(
1227+
offsets: &[T::Native],
1228+
) -> PrimitiveArray<T> {
1229+
let mut rows_number = Vec::with_capacity(
1230+
offsets[offsets.len() - 1].to_usize().unwrap() - offsets[0].to_usize().unwrap(),
1231+
);
1232+
1233+
for (i, w) in offsets.windows(2).enumerate() {
1234+
let len = w[1].as_usize() - w[0].as_usize();
1235+
rows_number.extend(repeat_n(T::Native::usize_as(i), len));
1236+
}
1237+
1238+
PrimitiveArray::new(rows_number.into(), None)
1239+
}
1240+
1241+
/// (2, 3) -> [0, 0, 1, 1, 2, 2]
1242+
fn fsl_values_row_number(list_size: i32, array_len: usize) -> Result<Int32Array> {
1243+
let list_size = list_size.to_usize().ok_or_else(|| {
1244+
_exec_datafusion_err!("fsl_values_index: invalid list_size {list_size}")
1245+
})?;
1246+
1247+
let mut rows_number = Vec::with_capacity(list_size * array_len);
1248+
1249+
for i in 0..array_len {
1250+
rows_number.extend(repeat_n(i as i32, list_size));
1251+
}
1252+
1253+
Ok(PrimitiveArray::new(rows_number.into(), None))
1254+
}
1255+
11841256
#[cfg(test)]
11851257
mod tests {
11861258
use std::sync::Arc;
@@ -1617,4 +1689,85 @@ mod tests {
16171689
assert_eq!(res.values(), expected.values());
16181690
assert_eq!(res.offsets(), expected.offsets());
16191691
}
1692+
1693+
#[test]
1694+
fn test_list_array_values_row_number() {
1695+
assert_eq!(
1696+
variable_size_list_values_row_number::<Int32Type>(
1697+
&OffsetBuffer::from_lengths([1, 3, 0, 2,])
1698+
),
1699+
Int32Array::from(vec![0, 1, 1, 1, 3, 3])
1700+
);
1701+
1702+
assert_eq!(
1703+
variable_size_list_values_row_number::<Int32Type>(
1704+
&OffsetBuffer::from_lengths([])
1705+
),
1706+
Int32Array::new_null(0)
1707+
);
1708+
1709+
assert_eq!(
1710+
variable_size_list_values_row_number::<Int32Type>(
1711+
&OffsetBuffer::from_lengths([0])
1712+
),
1713+
Int32Array::new_null(0)
1714+
);
1715+
1716+
assert_eq!(
1717+
variable_size_list_values_row_number::<Int32Type>(
1718+
&OffsetBuffer::from_lengths([0, 0])
1719+
),
1720+
Int32Array::new_null(0)
1721+
);
1722+
1723+
assert_eq!(
1724+
variable_size_list_values_row_number::<Int32Type>(
1725+
&OffsetBuffer::from_lengths([1])
1726+
),
1727+
Int32Array::from(vec![0])
1728+
);
1729+
1730+
assert_eq!(
1731+
variable_size_list_values_row_number::<Int32Type>(
1732+
&OffsetBuffer::from_lengths([2])
1733+
),
1734+
Int32Array::from(vec![0, 0])
1735+
);
1736+
}
1737+
1738+
#[test]
1739+
fn test_fsl_values_row_number() {
1740+
assert_eq!(
1741+
fsl_values_row_number(2, 3).unwrap(),
1742+
Int32Array::from(vec![0, 0, 1, 1, 2, 2])
1743+
);
1744+
1745+
assert_eq!(
1746+
fsl_values_row_number(1, 3).unwrap(),
1747+
Int32Array::from(vec![0, 1, 2])
1748+
);
1749+
1750+
assert_eq!(
1751+
fsl_values_row_number(2, 1).unwrap(),
1752+
Int32Array::from(vec![0, 0])
1753+
);
1754+
1755+
assert_eq!(
1756+
fsl_values_row_number(2, 0).unwrap(),
1757+
Int32Array::new_null(0),
1758+
);
1759+
1760+
assert_eq!(
1761+
fsl_values_row_number(0, 2).unwrap(),
1762+
Int32Array::new_null(0),
1763+
);
1764+
1765+
assert_eq!(
1766+
fsl_values_row_number(0, 0).unwrap(),
1767+
Int32Array::new_null(0),
1768+
);
1769+
1770+
fsl_values_row_number(-1, 2).unwrap_err();
1771+
fsl_values_row_number(-1, 0).unwrap_err();
1772+
}
16201773
}

datafusion/expr/src/execution_props.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::var_provider::{VarProvider, VarType};
1919
use chrono::{DateTime, Utc};
2020
use datafusion_common::HashMap;
2121
use datafusion_common::ScalarValue;
22+
use datafusion_common::TableReference;
2223
use datafusion_common::alias::AliasGenerator;
2324
use datafusion_common::config::ConfigOptions;
2425
use datafusion_common::{Result, internal_err};
@@ -69,6 +70,10 @@ pub struct ExecutionProps {
6970
/// Shared results container for uncorrelated scalar subquery values.
7071
/// Populated at execution time by `ScalarSubqueryExec`.
7172
pub subquery_results: ScalarSubqueryResults,
73+
/// Maps each lambda variable name to its lambda qualifier generated
74+
/// during physical planning. Populated by the physical planner for
75+
/// each lambda before calling `create_physical_expr`.
76+
pub lambda_variable_qualifier: HashMap<String, TableReference>,
7277
}
7378

7479
impl Default for ExecutionProps {
@@ -87,6 +92,7 @@ impl ExecutionProps {
8792
var_providers: None,
8893
subquery_indexes: HashMap::new(),
8994
subquery_results: ScalarSubqueryResults::default(),
95+
lambda_variable_qualifier: HashMap::new(),
9096
}
9197
}
9298

@@ -145,6 +151,22 @@ impl ExecutionProps {
145151
pub fn config_options(&self) -> Option<&Arc<ConfigOptions>> {
146152
self.config_options.as_ref()
147153
}
154+
155+
/// Adds a mapping for each variable to the given qualifier. Existing
156+
/// variables with conflicting names get's shadowed
157+
pub fn with_qualified_lambda_variables(
158+
mut self,
159+
qualifier: &TableReference,
160+
variables: &[String],
161+
) -> Self {
162+
for var in variables {
163+
self.lambda_variable_qualifier
164+
.entry_ref(var)
165+
.insert(qualifier.clone());
166+
}
167+
168+
self
169+
}
148170
}
149171

150172
/// Index of a scalar subquery within a [`ScalarSubqueryResults`] container.
@@ -252,7 +274,7 @@ mod test {
252274
fn debug() {
253275
let props = ExecutionProps::new();
254276
assert_eq!(
255-
"ExecutionProps { query_execution_start_time: None, alias_generator: AliasGenerator { next_id: 1 }, config_options: None, var_providers: None, subquery_indexes: {}, subquery_results: [] }",
277+
"ExecutionProps { query_execution_start_time: None, alias_generator: AliasGenerator { next_id: 1 }, config_options: None, var_providers: None, subquery_indexes: {}, subquery_results: [], lambda_variable_qualifier: {} }",
256278
format!("{props:?}")
257279
);
258280
}

0 commit comments

Comments
 (0)