This is an automated email from the ASF dual-hosted git repository.

agrove pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-python.git


The following commit(s) were added to refs/heads/main by this push:
     new faa5a3f  Tsaucer/find window fn (#747)
faa5a3f is described below

commit faa5a3f5352342328e183cbeceb3219d7bd05ff8
Author: Tim Saucer <[email protected]>
AuthorDate: Sat Jul 6 12:12:06 2024 -0400

    Tsaucer/find window fn (#747)
    
    * Add a search order when attempting to locate the appropriate window 
function
    
    * Remove unnecessary markings
    
    * Linting
    
    * Code cleanup
---
 examples/tpch/_tests.py                          | 31 ++++++++----
 examples/tpch/convert_data_to_parquet.py         |  3 +-
 examples/tpch/q08_market_share.py                |  4 +-
 examples/tpch/q09_product_type_profit_measure.py |  4 +-
 examples/tpch/q13_customer_distribution.py       |  4 +-
 examples/tpch/q14_promotion_effect.py            |  4 +-
 examples/tpch/q16_part_supplier_relationship.py  |  3 +-
 examples/tpch/q17_small_quantity_order.py        |  8 ++-
 examples/tpch/q20_potential_part_promotion.py    |  4 +-
 examples/tpch/q22_global_sales_opportunity.py    |  4 +-
 examples/tpch/util.py                            |  7 ++-
 src/functions.rs                                 | 64 +++++++++++++++++-------
 12 files changed, 100 insertions(+), 40 deletions(-)

diff --git a/examples/tpch/_tests.py b/examples/tpch/_tests.py
index 3f973d9..8804041 100644
--- a/examples/tpch/_tests.py
+++ b/examples/tpch/_tests.py
@@ -21,6 +21,7 @@ import pyarrow as pa
 from datafusion import col, lit, functions as F
 from util import get_answer_file
 
+
 def df_selection(col_name, col_type):
     if col_type == pa.float64() or isinstance(col_type, pa.Decimal128Type):
         return F.round(col(col_name), lit(2)).alias(col_name)
@@ -29,6 +30,7 @@ def df_selection(col_name, col_type):
     else:
         return col(col_name)
 
+
 def load_schema(col_name, col_type):
     if col_type == pa.int64() or col_type == pa.int32():
         return col_name, pa.string()
@@ -36,7 +38,8 @@ def load_schema(col_name, col_type):
         return col_name, pa.float64()
     else:
         return col_name, col_type
-    
+
+
 def expected_selection(col_name, col_type):
     if col_type == pa.int64() or col_type == pa.int32():
         return F.trim(col(col_name)).cast(col_type).alias(col_name)
@@ -45,20 +48,23 @@ def expected_selection(col_name, col_type):
     else:
         return col(col_name)
 
+
 def selections_and_schema(original_schema):
-    columns = [ (c, original_schema.field(c).type) for c in 
original_schema.names ]
+    columns = [(c, original_schema.field(c).type) for c in 
original_schema.names]
 
-    df_selections = [ df_selection(c, t) for (c, t) in columns]
-    expected_schema = [ load_schema(c, t) for (c, t) in columns]
-    expected_selections = [ expected_selection(c, t) for (c, t) in columns]
+    df_selections = [df_selection(c, t) for (c, t) in columns]
+    expected_schema = [load_schema(c, t) for (c, t) in columns]
+    expected_selections = [expected_selection(c, t) for (c, t) in columns]
 
     return (df_selections, expected_schema, expected_selections)
 
+
 def check_q17(df):
     raw_value = float(df.collect()[0]["avg_yearly"][0].as_py())
     value = round(raw_value, 2)
     assert abs(value - 348406.05) < 0.001
 
+
 @pytest.mark.parametrize(
     ("query_code", "answer_file"),
     [
@@ -72,9 +78,7 @@ def check_q17(df):
         ("q08_market_share", "q8"),
         ("q09_product_type_profit_measure", "q9"),
         ("q10_returned_item_reporting", "q10"),
-        pytest.param(
-            "q11_important_stock_identification", "q11", 
-        ),
+        ("q11_important_stock_identification", "q11"),
         ("q12_ship_mode_order_priority", "q12"),
         ("q13_customer_distribution", "q13"),
         ("q14_promotion_effect", "q14"),
@@ -97,13 +101,20 @@ def test_tpch_query_vs_answer_file(query_code: str, 
answer_file: str):
     if answer_file == "q17":
         return check_q17(df)
 
-    (df_selections, expected_schema, expected_selections) = 
selections_and_schema(df.schema())
+    (df_selections, expected_schema, expected_selections) = 
selections_and_schema(
+        df.schema()
+    )
 
     df = df.select(*df_selections)
 
     read_schema = pa.schema(expected_schema)
 
-    df_expected = module.ctx.read_csv(get_answer_file(answer_file), 
schema=read_schema, delimiter="|", file_extension=".out")
+    df_expected = module.ctx.read_csv(
+        get_answer_file(answer_file),
+        schema=read_schema,
+        delimiter="|",
+        file_extension=".out",
+    )
 
     df_expected = df_expected.select(*expected_selections)
 
diff --git a/examples/tpch/convert_data_to_parquet.py 
b/examples/tpch/convert_data_to_parquet.py
index d81ec29..a8091a7 100644
--- a/examples/tpch/convert_data_to_parquet.py
+++ b/examples/tpch/convert_data_to_parquet.py
@@ -117,7 +117,6 @@ all_schemas["supplier"] = [
 
 curr_dir = os.path.dirname(os.path.abspath(__file__))
 for filename, curr_schema in all_schemas.items():
-
     # For convenience, go ahead and convert the schema column names to 
lowercase
     curr_schema = [(s[0].lower(), s[1]) for s in curr_schema]
 
@@ -125,7 +124,7 @@ for filename, curr_schema in all_schemas.items():
     # in to handle the trailing | in the file
     output_cols = [r[0] for r in curr_schema]
 
-    curr_schema = [ pyarrow.field(r[0], r[1], nullable=False) for r in 
curr_schema]
+    curr_schema = [pyarrow.field(r[0], r[1], nullable=False) for r in 
curr_schema]
 
     # Trailing | requires extra field for in processing
     curr_schema.append(("some_null", pyarrow.null()))
diff --git a/examples/tpch/q08_market_share.py 
b/examples/tpch/q08_market_share.py
index d13a71d..cd6bc1f 100644
--- a/examples/tpch/q08_market_share.py
+++ b/examples/tpch/q08_market_share.py
@@ -47,7 +47,9 @@ end_date = lit(datetime.strptime(END_DATE, "%Y-%m-%d").date())
 
 ctx = SessionContext()
 
-df_part = 
ctx.read_parquet(get_data_path("part.parquet")).select_columns("p_partkey", 
"p_type")
+df_part = ctx.read_parquet(get_data_path("part.parquet")).select_columns(
+    "p_partkey", "p_type"
+)
 df_supplier = 
ctx.read_parquet(get_data_path("supplier.parquet")).select_columns(
     "s_suppkey", "s_nationkey"
 )
diff --git a/examples/tpch/q09_product_type_profit_measure.py 
b/examples/tpch/q09_product_type_profit_measure.py
index 29ffcee..b4a7369 100644
--- a/examples/tpch/q09_product_type_profit_measure.py
+++ b/examples/tpch/q09_product_type_profit_measure.py
@@ -39,7 +39,9 @@ part_color = lit("green")
 
 ctx = SessionContext()
 
-df_part = 
ctx.read_parquet(get_data_path("part.parquet")).select_columns("p_partkey", 
"p_name")
+df_part = ctx.read_parquet(get_data_path("part.parquet")).select_columns(
+    "p_partkey", "p_name"
+)
 df_supplier = 
ctx.read_parquet(get_data_path("supplier.parquet")).select_columns(
     "s_suppkey", "s_nationkey"
 )
diff --git a/examples/tpch/q13_customer_distribution.py 
b/examples/tpch/q13_customer_distribution.py
index 2b6e7e2..bc0a5bd 100644
--- a/examples/tpch/q13_customer_distribution.py
+++ b/examples/tpch/q13_customer_distribution.py
@@ -41,7 +41,9 @@ ctx = SessionContext()
 df_orders = ctx.read_parquet(get_data_path("orders.parquet")).select_columns(
     "o_custkey", "o_comment"
 )
-df_customer = 
ctx.read_parquet(get_data_path("customer.parquet")).select_columns("c_custkey")
+df_customer = 
ctx.read_parquet(get_data_path("customer.parquet")).select_columns(
+    "c_custkey"
+)
 
 # Use a regex to remove special cases
 df_orders = df_orders.filter(
diff --git a/examples/tpch/q14_promotion_effect.py 
b/examples/tpch/q14_promotion_effect.py
index 75fa363..8cb1e4c 100644
--- a/examples/tpch/q14_promotion_effect.py
+++ b/examples/tpch/q14_promotion_effect.py
@@ -44,7 +44,9 @@ ctx = SessionContext()
 df_lineitem = 
ctx.read_parquet(get_data_path("lineitem.parquet")).select_columns(
     "l_partkey", "l_shipdate", "l_extendedprice", "l_discount"
 )
-df_part = 
ctx.read_parquet(get_data_path("part.parquet")).select_columns("p_partkey", 
"p_type")
+df_part = ctx.read_parquet(get_data_path("part.parquet")).select_columns(
+    "p_partkey", "p_type"
+)
 
 
 # Check part type begins with PROMO
diff --git a/examples/tpch/q16_part_supplier_relationship.py 
b/examples/tpch/q16_part_supplier_relationship.py
index 0db2d1b..fdcb5b4 100644
--- a/examples/tpch/q16_part_supplier_relationship.py
+++ b/examples/tpch/q16_part_supplier_relationship.py
@@ -62,7 +62,8 @@ df_partsupp = df_partsupp.join(
 # Select the parts we are interested in
 df_part = df_part.filter(col("p_brand") != lit(BRAND))
 df_part = df_part.filter(
-    F.substring(col("p_type"), lit(0), lit(len(TYPE_TO_IGNORE) + 1)) != 
lit(TYPE_TO_IGNORE)
+    F.substring(col("p_type"), lit(0), lit(len(TYPE_TO_IGNORE) + 1))
+    != lit(TYPE_TO_IGNORE)
 )
 
 # Python conversion of integer to literal casts it to int64 but the data for
diff --git a/examples/tpch/q17_small_quantity_order.py 
b/examples/tpch/q17_small_quantity_order.py
index 5880e7e..e0ee8bb 100644
--- a/examples/tpch/q17_small_quantity_order.py
+++ b/examples/tpch/q17_small_quantity_order.py
@@ -56,7 +56,13 @@ df = df.join(df_lineitem, (["p_partkey"], ["l_partkey"]), 
"inner")
 # Find the average quantity
 window_frame = WindowFrame("rows", None, None)
 df = df.with_column(
-    "avg_quantity", F.window("avg", [col("l_quantity")], 
window_frame=window_frame, partition_by=[col("l_partkey")])
+    "avg_quantity",
+    F.window(
+        "avg",
+        [col("l_quantity")],
+        window_frame=window_frame,
+        partition_by=[col("l_partkey")],
+    ),
 )
 
 df = df.filter(col("l_quantity") < lit(0.2) * col("avg_quantity"))
diff --git a/examples/tpch/q20_potential_part_promotion.py 
b/examples/tpch/q20_potential_part_promotion.py
index 85e7226..05a2674 100644
--- a/examples/tpch/q20_potential_part_promotion.py
+++ b/examples/tpch/q20_potential_part_promotion.py
@@ -40,7 +40,9 @@ NATION_OF_INTEREST = "CANADA"
 
 ctx = SessionContext()
 
-df_part = 
ctx.read_parquet(get_data_path("part.parquet")).select_columns("p_partkey", 
"p_name")
+df_part = ctx.read_parquet(get_data_path("part.parquet")).select_columns(
+    "p_partkey", "p_name"
+)
 df_lineitem = 
ctx.read_parquet(get_data_path("lineitem.parquet")).select_columns(
     "l_shipdate", "l_partkey", "l_suppkey", "l_quantity"
 )
diff --git a/examples/tpch/q22_global_sales_opportunity.py 
b/examples/tpch/q22_global_sales_opportunity.py
index dfde19c..622c142 100644
--- a/examples/tpch/q22_global_sales_opportunity.py
+++ b/examples/tpch/q22_global_sales_opportunity.py
@@ -38,7 +38,9 @@ ctx = SessionContext()
 df_customer = 
ctx.read_parquet(get_data_path("customer.parquet")).select_columns(
     "c_phone", "c_acctbal", "c_custkey"
 )
-df_orders = 
ctx.read_parquet(get_data_path("orders.parquet")).select_columns("o_custkey")
+df_orders = ctx.read_parquet(get_data_path("orders.parquet")).select_columns(
+    "o_custkey"
+)
 
 # The nation code is a two digit number, but we need to convert it to a string 
literal
 nation_codes = F.make_array(*[lit(str(n)) for n in NATION_CODES])
diff --git a/examples/tpch/util.py b/examples/tpch/util.py
index 191fa60..7e3d659 100644
--- a/examples/tpch/util.py
+++ b/examples/tpch/util.py
@@ -20,14 +20,17 @@ Common utilities for running TPC-H examples.
 """
 
 import os
-from pathlib import Path
+
 
 def get_data_path(filename: str) -> str:
     path = os.path.dirname(os.path.abspath(__file__))
 
     return os.path.join(path, "data", filename)
 
+
 def get_answer_file(answer_file: str) -> str:
     path = os.path.dirname(os.path.abspath(__file__))
 
-    return os.path.join(path, "../../benchmarks/tpch/data/answers", 
f"{answer_file}.out")
+    return os.path.join(
+        path, "../../benchmarks/tpch/data/answers", f"{answer_file}.out"
+    )
diff --git a/src/functions.rs b/src/functions.rs
index b39d98b..0ec0d5e 100644
--- a/src/functions.rs
+++ b/src/functions.rs
@@ -15,6 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
+use datafusion::functions_aggregate::all_default_aggregate_functions;
 use pyo3::{prelude::*, wrap_pyfunction};
 
 use crate::common::data_type::NullTreatment;
@@ -311,6 +312,50 @@ fn case(expr: PyExpr) -> PyResult<PyCaseBuilder> {
     })
 }
 
+/// Helper function to find the appropriate window function. First, if a 
session
+/// context is defined check it's registered functions. If no context is 
defined,
+/// attempt to find from all default functions. Lastly, as a fall back attempt
+/// to use built in window functions, which are being deprecated.
+fn find_window_fn(name: &str, ctx: Option<PySessionContext>) -> 
PyResult<WindowFunctionDefinition> {
+    let mut maybe_fn = match &ctx {
+        Some(ctx) => {
+            let session_state = ctx.ctx.state();
+
+            match session_state.window_functions().contains_key(name) {
+                true => session_state
+                    .window_functions()
+                    .get(name)
+                    .map(|f| WindowFunctionDefinition::WindowUDF(f.clone())),
+                false => session_state
+                    .aggregate_functions()
+                    .get(name)
+                    .map(|f| 
WindowFunctionDefinition::AggregateUDF(f.clone())),
+            }
+        }
+        None => {
+            let default_aggregate_fns = all_default_aggregate_functions();
+
+            default_aggregate_fns
+                .iter()
+                .find(|v| v.aliases().contains(&name.to_string()))
+                .map(|f| WindowFunctionDefinition::AggregateUDF(f.clone()))
+        }
+    };
+
+    if maybe_fn.is_none() {
+        maybe_fn = find_df_window_func(name).or_else(|| {
+            ctx.and_then(|ctx| {
+                ctx.ctx
+                    .udaf(name)
+                    .map(WindowFunctionDefinition::AggregateUDF)
+                    .ok()
+            })
+        });
+    }
+
+    maybe_fn.ok_or(DataFusionError::Common("window function not 
found".to_string()).into())
+}
+
 /// Creates a new Window function expression
 #[pyfunction]
 fn window(
@@ -321,24 +366,7 @@ fn window(
     window_frame: Option<PyWindowFrame>,
     ctx: Option<PySessionContext>,
 ) -> PyResult<PyExpr> {
-    // workaround for https://github.com/apache/datafusion-python/issues/730
-    let fun = if name == "sum" {
-        let sum_udf = functions_aggregate::sum::sum_udaf();
-        Some(WindowFunctionDefinition::AggregateUDF(sum_udf))
-    } else {
-        find_df_window_func(name).or_else(|| {
-            ctx.and_then(|ctx| {
-                ctx.ctx
-                    .udaf(name)
-                    .map(WindowFunctionDefinition::AggregateUDF)
-                    .ok()
-            })
-        })
-    };
-    if fun.is_none() {
-        return Err(DataFusionError::Common("window function not 
found".to_string()).into());
-    }
-    let fun = fun.unwrap();
+    let fun = find_window_fn(name, ctx)?;
     let window_frame = window_frame
         .unwrap_or_else(|| PyWindowFrame::new("rows", None, Some(0)).unwrap())
         .into();


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to