Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions benchmarks/compress-bench/src/vortex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use vortex::expr::root;
use vortex::expr::select;
use vortex::file::OpenOptionsSessionExt;
use vortex::file::WriteOptionsSessionExt;
use vortex::layout::scan::scan_builder::optimize_and_bind;
use vortex_arrow::ToArrowType;
use vortex_bench::Format;
use vortex_bench::SESSION;
Expand Down Expand Up @@ -64,14 +65,15 @@ impl Compressor for VortexCompressor {
let start = Instant::now();
let data = Bytes::from(buf);
let mut scan = SESSION.open_options().open_buffer(data)?.scan()?;
let root_columns = scan
.dtype()?
let source_dtype = scan.dtype()?;
let root_columns = source_dtype
.as_struct_fields_opt()
.map_or(0, |fields| fields.nfields());
if let Some(cols) = read_projection(root_columns) {
// Columns are named "0".."num_columns-1"; project the given subset.
let names: FieldNames = cols.iter().map(|i| i.to_string()).collect();
scan = scan.with_projection(select(names, root()));
let projection = optimize_and_bind(select(names, root()), &source_dtype)?;
scan = scan.with_projection(projection);
}
let schema = Arc::new(scan.dtype()?.to_arrow_schema()?);

Expand Down
3 changes: 2 additions & 1 deletion docs/developer-guide/internals/session.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ session.write_options()
.await?;

// Scanning a layout
let filter = optimize_and_bind(expr, layout_reader.dtype())?;
ScanBuilder::new(session.clone(), layout_reader)
.with_filter(expr)
.with_filter(filter)
.into_array_stream()?;
```

Expand Down
16 changes: 12 additions & 4 deletions fuzz/fuzz_targets/file_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use itertools::Itertools;
use libfuzzer_sys::Corpus;
use libfuzzer_sys::fuzz_target;
use vortex::layout::scan::scan_builder::optimize_and_bind;
use vortex_array::Canonical;
use vortex_array::IntoArray;
use vortex_array::VortexSessionExecute;
Expand Down Expand Up @@ -78,14 +79,21 @@ fuzz_target!(|fuzz: FuzzFileAction| -> Corpus {
.write(&mut full_buff, array_data.to_array_iterator())
.vortex_expect("file write should succeed in fuzz test");

let mut output = SESSION
let file = SESSION
.open_options()
.open_buffer(full_buff)
.vortex_expect("open_buffer should succeed in fuzz test")
.vortex_expect("open_buffer should succeed in fuzz test");
let projection = optimize_and_bind(projection_expr.unwrap_or_else(root), file.dtype())
.vortex_expect("projection should bind in fuzz test");
let filter = filter_expr
.map(|filter| optimize_and_bind(filter, file.dtype()))
.transpose()
.vortex_expect("filter should bind in fuzz test");
let mut output = file
.scan()
.vortex_expect("scan should succeed in fuzz test")
.with_projection(projection_expr.unwrap_or_else(root))
.with_some_filter(filter_expr)
.with_projection(projection)
.with_some_filter(filter)
.into_array_iter(&*RUNTIME)
.vortex_expect("into_array_iter should succeed in fuzz test")
.try_collect::<_, Vec<_>, _>()
Expand Down
3 changes: 2 additions & 1 deletion vortex-array/src/arrays/scalar_fn/vtable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use crate::dtype::DType;
use crate::executor::ExecutionCtx;
use crate::executor::ExecutionResult;
use crate::expr::Expression;
use crate::expr::display::ExprDisplay;
use crate::matcher::Matcher;
use crate::scalar_fn;
use crate::scalar_fn::Arity;
Expand Down Expand Up @@ -309,7 +310,7 @@ impl scalar_fn::ScalarFnVTable for ArrayExpr {
fn fmt_sql(
&self,
options: &Self::Options,
_expr: &Expression,
_expr: &dyn ExprDisplay,
f: &mut Formatter<'_>,
) -> std::fmt::Result {
write!(f, "{}", options.0.encoding_id())
Expand Down
87 changes: 44 additions & 43 deletions vortex-array/src/expr/bound_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ use itertools::Itertools;
use vortex_error::VortexExpect;
use vortex_error::VortexResult;
use vortex_error::vortex_ensure;
use vortex_session::VortexSession;

use crate::dtype::DType;
use crate::expr::Expression;
use crate::expr::display::DisplayTreeExpr;
use crate::expr::scope::Scope;
use crate::scalar_fn::ScalarFnRef;
use crate::scalar_fn::ScalarFnVTable;
use crate::scalar_fn::fns::root::Root;
use crate::stats::rewrite::StatsRewriteCtx;

/// An [`Expression`] that has been type-checked against a [`Scope`].
///
Expand Down Expand Up @@ -171,6 +174,11 @@ impl BoundExpression {
}
}

/// Return the child at `index`.
pub fn child(&self, index: usize) -> &BoundExpression {
&self.children()[index]
}

/// The scalar function for this node, or `None` if it is the scope root.
pub fn as_scalar(&self) -> Option<&ScalarFnRef> {
match &self.kind {
Expand All @@ -179,56 +187,53 @@ impl BoundExpression {
}
}

/// Return whether this node uses the given scalar-function vtable.
pub fn is<V: ScalarFnVTable>(&self) -> bool {
self.as_scalar().is_some_and(ScalarFnRef::is::<V>)
}

/// Return the typed scalar-function options when this node uses the given vtable.
pub fn as_opt<V: ScalarFnVTable>(&self) -> Option<&V::Options> {
self.as_scalar().and_then(ScalarFnRef::as_opt::<V>)
}

/// Return the typed scalar-function options for this node.
///
/// # Panics
///
/// Panics when this node is the scope root or uses a different scalar-function vtable.
pub fn as_<V: ScalarFnVTable>(&self) -> &V::Options {
self.as_opt::<V>()
.vortex_expect("Bound expression options type mismatch")
}

/// Whether this node is the scope root.
pub fn is_root(&self) -> bool {
matches!(self.kind, BoundKind::Root)
}

/// Display the bound expression as a formatted tree structure.
pub fn display_tree(&self) -> impl Display {
DisplayTreeExpr(self)
/// Return an expression that proves this predicate is definitely false from statistics.
pub fn falsify(&self, session: &VortexSession) -> VortexResult<Option<BoundExpression>> {
StatsRewriteCtx::new(session).falsify(self)
}

/// Convert this bound tree back into its unbound logical representation.
///
/// This rebuilds the expression iteratively; the bound representation does not retain a
/// second expression tree.
// TODO: This is temporary artifact of the migration from using `Expression`s to
// `BoundExpression`s
pub fn unbind(&self) -> Expression {
let mut pending = vec![(self, false)];
let mut expressions = Vec::new();

while let Some((node, visited)) = pending.pop() {
match node.kind() {
BoundKind::Root => expressions.push(crate::expr::root()),
BoundKind::Scalar {
scalar_fn,
children,
} if visited => {
let child_start = expressions.len() - children.len();
let child_expressions = expressions.split_off(child_start);
expressions.push(
Expression::try_new(scalar_fn.clone(), child_expressions)
.vortex_expect("a bound expression always has valid arity"),
);
}
BoundKind::Scalar { children, .. } => {
pending.push((node, true));
pending.extend(children.iter().rev().map(|child| (child, false)));
}
}
}
/// Return an expression that proves this predicate is definitely true from statistics.
pub fn satisfy(&self, session: &VortexSession) -> VortexResult<Option<BoundExpression>> {
StatsRewriteCtx::new(session).satisfy(self)
}

expressions
.pop()
.vortex_expect("binding always produces one expression root")
/// Display the bound expression as a formatted tree structure.
pub fn display_tree(&self) -> impl Display {
DisplayTreeExpr(self)
}
}

impl Display for BoundExpression {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.unbind(), f)
match self.kind() {
BoundKind::Scalar { scalar_fn, .. } => scalar_fn.fmt_sql(self, f),
BoundKind::Root => f.write_str("$"),
}
}
}

Expand Down Expand Up @@ -300,7 +305,7 @@ mod tests {
let bound = root().bind_scope(&scope())?;
assert!(bound.is_root());
assert_eq!(bound.dtype(), &struct_dtype());
assert_eq!(bound.unbind(), root());
assert_eq!(bound, BoundExpression::new_root(struct_dtype()));
Ok(())
}

Expand Down Expand Up @@ -375,11 +380,7 @@ mod tests {

assert_eq!(bound, independently_bound);
assert_eq!(ExactBoundExpr(bound.clone()), ExactBoundExpr(bound.clone()));
assert_ne!(
ExactBoundExpr(bound.clone()),
ExactBoundExpr(independently_bound)
);
assert_eq!(bound.unbind(), expr);
assert_ne!(ExactBoundExpr(bound), ExactBoundExpr(independently_bound));
Ok(())
}

Expand Down
37 changes: 37 additions & 0 deletions vortex-array/src/expr/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,43 @@ pub enum DisplayFormat {
Tree,
}

/// Read-only expression-tree interface used by scalar functions for SQL-style formatting.
///
/// Both [`Expression`] and [`BoundExpression`] implement this interface, allowing scalar
/// functions to format either representation without converting between them.
pub trait ExprDisplay: Display {
/// Return the child at `index`.
fn display_child(&self, index: usize) -> &dyn ExprDisplay;

/// Return the number of children in this node.
fn display_children_count(&self) -> usize;

/// Format the child at `index` using its compact SQL-style representation.
fn fmt_display_child(&self, index: usize, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(self.display_child(index), f)
}
}

impl ExprDisplay for Expression {
fn display_child(&self, index: usize) -> &dyn ExprDisplay {
Expression::child(self, index)
}

fn display_children_count(&self) -> usize {
self.children().len()
}
}

impl ExprDisplay for BoundExpression {
fn display_child(&self, index: usize) -> &dyn ExprDisplay {
&self.children()[index]
}

fn display_children_count(&self) -> usize {
self.children().len()
}
}

trait DisplayTreeNode: Sized {
fn tree_children(&self) -> &[Self];

Expand Down
32 changes: 1 addition & 31 deletions vortex-array/src/expr/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ use std::sync::Arc;
use itertools::Itertools;
use vortex_error::VortexResult;
use vortex_error::vortex_ensure;
use vortex_session::VortexSession;

use crate::dtype::DType;
use crate::expr::display::DisplayTreeExpr;
use crate::scalar_fn::ScalarFnRef;
use crate::scalar_fn::fns::root::Root;
use crate::stats::rewrite::StatsRewriteCtx;

/// A node in a Vortex expression tree.
///
Expand Down Expand Up @@ -113,40 +111,12 @@ impl Expression {
self.scalar_fn.validity(self)
}

/// Returns an expression that proves this predicate is definitely false from stats.
///
/// `scope` is the dtype of the row this expression evaluates over.
///
/// If the returned expression evaluates to `true` for a stats scope, this expression is
/// guaranteed to be false for every row in that scope. `false` and `null` are unknown.
pub fn falsify(
&self,
scope: &DType,
session: &VortexSession,
) -> VortexResult<Option<Expression>> {
StatsRewriteCtx::new(session, scope).falsify(self)
}

/// Returns an expression that proves this predicate is definitely true from stats.
///
/// `scope` is the dtype of the row this expression evaluates over.
///
/// If the returned expression evaluates to `true` for a stats scope, this expression is
/// guaranteed to be true for every row in that scope. `false` and `null` are unknown.
pub fn satisfy(
&self,
scope: &DType,
session: &VortexSession,
) -> VortexResult<Option<Expression>> {
StatsRewriteCtx::new(session, scope).satisfy(self)
}

/// Format the expression as a compact string.
///
/// Since this is a recursive formatter, it is exposed on the public Expression type.
/// See fmt_data that is only implemented on the vtable trait.
pub fn fmt_sql(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.scalar_fn().fmt_sql(self, f)
self.scalar_fn.fmt_sql(self, f)
}

/// Display the expression as a formatted tree structure.
Expand Down
Loading
Loading