1use arrow_array::{
17 Array, Float64Array, Int64Array, RecordBatch, StringArray,
18};
19use arrow_schema::{DataType as ArrowDt, Field, Schema};
20use lex_bytecode::Value;
21use polars::prelude::{
22 col, lit, Column, DataFrame, DataType as PlDt, Expr, IntoLazy, JoinArgs,
23 JoinType, NamedFrom, PlSmallStr, Series, SortMultipleOptions,
24};
25use polars::prelude::IntoColumn;
26use std::collections::VecDeque;
27use std::sync::Arc;
28
29fn err<T>(s: impl Into<String>) -> Result<T, String> { Err(s.into()) }
32
33fn expect_table(v: Option<&Value>) -> Result<&Arc<RecordBatch>, String> {
34 match v {
35 Some(Value::ArrowTable(t)) => Ok(t),
36 Some(other) => err(format!("df: expected arrow.Table, got {other:?}")),
37 None => err("df: expected arrow.Table, got nothing"),
38 }
39}
40
41fn expect_str(v: Option<&Value>) -> Result<&str, String> {
42 match v {
43 Some(Value::Str(s)) => Ok(s.as_str()),
44 Some(other) => err(format!("df: expected Str, got {other:?}")),
45 None => err("df: expected Str, got nothing"),
46 }
47}
48
49fn expect_int(v: Option<&Value>) -> Result<i64, String> {
50 match v {
51 Some(Value::Int(n)) => Ok(*n),
52 Some(other) => err(format!("df: expected Int, got {other:?}")),
53 None => err("df: expected Int, got nothing"),
54 }
55}
56
57fn expect_float(v: Option<&Value>) -> Result<f64, String> {
58 match v {
59 Some(Value::Float(f)) => Ok(*f),
60 Some(Value::Int(n)) => Ok(*n as f64),
61 Some(other) => err(format!("df: expected Float, got {other:?}")),
62 None => err("df: expected Float, got nothing"),
63 }
64}
65
66fn expect_bool(v: Option<&Value>) -> Result<bool, String> {
67 match v {
68 Some(Value::Bool(b)) => Ok(*b),
69 Some(other) => err(format!("df: expected Bool, got {other:?}")),
70 None => err("df: expected Bool, got nothing"),
71 }
72}
73
74fn expect_list(v: Option<&Value>) -> Result<&VecDeque<Value>, String> {
75 match v {
76 Some(Value::List(items)) => Ok(items),
77 Some(other) => err(format!("df: expected List, got {other:?}")),
78 None => err("df: expected List, got nothing"),
79 }
80}
81
82fn to_polars(rb: &RecordBatch) -> Result<DataFrame, String> {
89 let mut cols: Vec<Column> = Vec::with_capacity(rb.num_columns());
90 for (idx, field) in rb.schema().fields().iter().enumerate() {
91 let name = field.name();
92 let arr = rb.column(idx);
93 let s = match arr.data_type() {
94 ArrowDt::Int64 => {
95 let a = arr.as_any().downcast_ref::<Int64Array>().unwrap();
96 let buf: Vec<Option<i64>> = (0..a.len()).map(|i|
97 if a.is_null(i) { None } else { Some(a.value(i)) }
98 ).collect();
99 Series::new(PlSmallStr::from_str(name), buf)
100 }
101 ArrowDt::Float64 => {
102 let a = arr.as_any().downcast_ref::<Float64Array>().unwrap();
103 let buf: Vec<Option<f64>> = (0..a.len()).map(|i|
104 if a.is_null(i) { None } else { Some(a.value(i)) }
105 ).collect();
106 Series::new(PlSmallStr::from_str(name), buf)
107 }
108 ArrowDt::Utf8 => {
109 let a = arr.as_any().downcast_ref::<StringArray>().unwrap();
110 let buf: Vec<Option<&str>> = (0..a.len()).map(|i|
111 if a.is_null(i) { None } else { Some(a.value(i)) }
112 ).collect();
113 Series::new(PlSmallStr::from_str(name), buf)
114 }
115 other => return err(format!(
116 "df: column `{name}` has unsupported type {other:?} (v1: Int64/Float64/Utf8)")),
117 };
118 cols.push(s.into());
119 }
120 DataFrame::new_infer_height(cols).map_err(|e| format!("df: build DataFrame: {e}"))
124}
125
126fn from_polars(df: &DataFrame) -> Result<RecordBatch, String> {
132 let mut fields: Vec<Field> = Vec::with_capacity(df.width());
133 let mut arrays: Vec<arrow_array::ArrayRef> = Vec::with_capacity(df.width());
134 for column in df.columns() {
135 let name = column.name().as_str();
136 let s = column.as_materialized_series();
137 let (field, array): (Field, arrow_array::ArrayRef) = match s.dtype() {
138 PlDt::Int64 => {
139 let v: Vec<Option<i64>> = s.i64()
140 .map_err(|e| format!("df: column `{name}` as i64: {e}"))?
141 .iter().collect();
142 (
143 Field::new(name, ArrowDt::Int64, true),
144 Arc::new(Int64Array::from(v)),
145 )
146 }
147 PlDt::Float64 => {
148 let v: Vec<Option<f64>> = s.f64()
149 .map_err(|e| format!("df: column `{name}` as f64: {e}"))?
150 .iter().collect();
151 (
152 Field::new(name, ArrowDt::Float64, true),
153 Arc::new(Float64Array::from(v)),
154 )
155 }
156 PlDt::String => {
157 let v: Vec<Option<String>> = s.str()
158 .map_err(|e| format!("df: column `{name}` as Utf8: {e}"))?
159 .iter().map(|x| x.map(|s| s.to_string())).collect();
160 (
161 Field::new(name, ArrowDt::Utf8, true),
162 Arc::new(StringArray::from(v)),
163 )
164 }
165 PlDt::UInt32 => {
168 let v: Vec<Option<i64>> = s.u32()
169 .map_err(|e| format!("df: column `{name}` as u32: {e}"))?
170 .iter().map(|x| x.map(|n| n as i64)).collect();
171 (
172 Field::new(name, ArrowDt::Int64, true),
173 Arc::new(Int64Array::from(v)),
174 )
175 }
176 other => return err(format!(
177 "df: polars column `{name}` has unsupported type {other:?}")),
178 };
179 fields.push(field);
180 arrays.push(array);
181 }
182 let schema = Arc::new(Schema::new(fields));
183 RecordBatch::try_new(schema, arrays)
184 .map_err(|e| format!("df: RecordBatch::try_new: {e}"))
185}
186
187fn pack(df: DataFrame) -> Result<Value, String> {
190 let rb = from_polars(&df)?;
191 Ok(Value::ArrowTable(Arc::new(rb)))
192}
193
194fn filter_eq_int(args: &[Value]) -> Result<Value, String> {
195 let rb = expect_table(args.first())?;
196 let col_name = expect_str(args.get(1))?;
197 let needle = expect_int(args.get(2))?;
198 let df = to_polars(rb)?;
199 let out = df.lazy()
200 .filter(col(col_name).eq(lit(needle)))
201 .collect()
202 .map_err(|e| format!("df.filter_eq_int: {e}"))?;
203 pack(out)
204}
205
206fn filter_gt_int(args: &[Value]) -> Result<Value, String> {
207 let rb = expect_table(args.first())?;
208 let col_name = expect_str(args.get(1))?;
209 let needle = expect_int(args.get(2))?;
210 let df = to_polars(rb)?;
211 let out = df.lazy()
212 .filter(col(col_name).gt(lit(needle)))
213 .collect()
214 .map_err(|e| format!("df.filter_gt_int: {e}"))?;
215 pack(out)
216}
217
218fn filter_lt_int(args: &[Value]) -> Result<Value, String> {
219 let rb = expect_table(args.first())?;
220 let col_name = expect_str(args.get(1))?;
221 let needle = expect_int(args.get(2))?;
222 let df = to_polars(rb)?;
223 let out = df.lazy()
224 .filter(col(col_name).lt(lit(needle)))
225 .collect()
226 .map_err(|e| format!("df.filter_lt_int: {e}"))?;
227 pack(out)
228}
229
230fn expect_col_type(rb: &RecordBatch, col_name: &str, wanted: ArrowDt, op: &str) -> Result<(), String> {
237 let schema = rb.schema();
238 let (_, field) = schema
239 .column_with_name(col_name)
240 .ok_or_else(|| format!("df.{op}: column `{col_name}` not found"))?;
241 if field.data_type() != &wanted {
242 return err(format!(
243 "df.{op}: expected {wanted:?} column, got {:?}",
244 field.data_type()
245 ));
246 }
247 Ok(())
248}
249
250fn filter_eq_str(args: &[Value]) -> Result<Value, String> {
251 let rb = expect_table(args.first())?;
252 let col_name = expect_str(args.get(1))?;
253 let needle = expect_str(args.get(2))?;
254 expect_col_type(rb, col_name, ArrowDt::Utf8, "filter_eq_str")?;
255 let df = to_polars(rb)?;
256 let out = df.lazy()
257 .filter(col(col_name).eq(lit(needle.to_string())))
258 .collect()
259 .map_err(|e| format!("df.filter_eq_str: {e}"))?;
260 pack(out)
261}
262
263fn filter_in_str(args: &[Value]) -> Result<Value, String> {
264 let rb = expect_table(args.first())?;
265 let col_name = expect_str(args.get(1))?;
266 let needles_list = expect_list(args.get(2))?;
267 expect_col_type(rb, col_name, ArrowDt::Utf8, "filter_in_str")?;
268 let mut needles: Vec<String> = Vec::with_capacity(needles_list.len());
269 for v in needles_list {
270 match v {
271 Value::Str(s) => needles.push(s.to_string()),
272 other => return err(format!(
273 "df.filter_in_str: needle list contained non-Str: {other:?}")),
274 }
275 }
276 if needles.is_empty() {
278 let empty = RecordBatch::new_empty(rb.schema());
281 return Ok(Value::ArrowTable(Arc::new(empty)));
282 }
283 let df = to_polars(rb)?;
284 let needle_series: Series =
285 Series::new(PlSmallStr::from_static("__in"), needles).into_column().take_materialized_series();
286 let out = df.lazy()
287 .filter(col(col_name).is_in(lit(needle_series), false))
288 .collect()
289 .map_err(|e| format!("df.filter_in_str: {e}"))?;
290 pack(out)
291}
292
293fn filter_eq_float(args: &[Value]) -> Result<Value, String> {
294 let rb = expect_table(args.first())?;
295 let col_name = expect_str(args.get(1))?;
296 let needle = expect_float(args.get(2))?;
297 expect_col_type(rb, col_name, ArrowDt::Float64, "filter_eq_float")?;
298 let df = to_polars(rb)?;
299 let out = df.lazy()
300 .filter(col(col_name).eq(lit(needle)))
301 .collect()
302 .map_err(|e| format!("df.filter_eq_float: {e}"))?;
303 pack(out)
304}
305
306fn filter_lt_float(args: &[Value]) -> Result<Value, String> {
307 let rb = expect_table(args.first())?;
308 let col_name = expect_str(args.get(1))?;
309 let needle = expect_float(args.get(2))?;
310 expect_col_type(rb, col_name, ArrowDt::Float64, "filter_lt_float")?;
311 let df = to_polars(rb)?;
312 let out = df.lazy()
313 .filter(col(col_name).lt(lit(needle)))
314 .collect()
315 .map_err(|e| format!("df.filter_lt_float: {e}"))?;
316 pack(out)
317}
318
319fn filter_gt_float(args: &[Value]) -> Result<Value, String> {
320 let rb = expect_table(args.first())?;
321 let col_name = expect_str(args.get(1))?;
322 let needle = expect_float(args.get(2))?;
323 expect_col_type(rb, col_name, ArrowDt::Float64, "filter_gt_float")?;
324 let df = to_polars(rb)?;
325 let out = df.lazy()
326 .filter(col(col_name).gt(lit(needle)))
327 .collect()
328 .map_err(|e| format!("df.filter_gt_float: {e}"))?;
329 pack(out)
330}
331
332fn filter_isnull(args: &[Value]) -> Result<Value, String> {
333 let rb = expect_table(args.first())?;
334 let col_name = expect_str(args.get(1))?;
335 if rb.schema().column_with_name(col_name).is_none() {
337 return err(format!("df.filter_isnull: column `{col_name}` not found"));
338 }
339 let df = to_polars(rb)?;
340 let out = df.lazy()
341 .filter(col(col_name).is_null())
342 .collect()
343 .map_err(|e| format!("df.filter_isnull: {e}"))?;
344 pack(out)
345}
346
347fn filter_notnull(args: &[Value]) -> Result<Value, String> {
348 let rb = expect_table(args.first())?;
349 let col_name = expect_str(args.get(1))?;
350 if rb.schema().column_with_name(col_name).is_none() {
351 return err(format!("df.filter_notnull: column `{col_name}` not found"));
352 }
353 let df = to_polars(rb)?;
354 let out = df.lazy()
355 .filter(col(col_name).is_not_null())
356 .collect()
357 .map_err(|e| format!("df.filter_notnull: {e}"))?;
358 pack(out)
359}
360
361fn drop_nulls(args: &[Value]) -> Result<Value, String> {
362 let rb = expect_table(args.first())?;
363 let cols_list = expect_list(args.get(1))?;
364 if cols_list.is_empty() {
366 return Ok(Value::ArrowTable(Arc::clone(rb)));
367 }
368 let mut cols: Vec<String> = Vec::with_capacity(cols_list.len());
369 {
370 let schema = rb.schema();
371 for v in cols_list {
372 match v {
373 Value::Str(s) => {
374 if schema.column_with_name(s.as_str()).is_none() {
375 return err(format!("df.drop_nulls: column `{s}` not found"));
376 }
377 cols.push(s.to_string());
378 }
379 other => return err(format!(
380 "df.drop_nulls: column list contained non-Str: {other:?}")),
381 }
382 }
383 }
384 let df = to_polars(rb)?;
385 let out = df
386 .drop_nulls(Some(&cols))
387 .map_err(|e| format!("df.drop_nulls: {e}"))?;
388 pack(out)
389}
390
391fn sort_by(args: &[Value]) -> Result<Value, String> {
392 let rb = expect_table(args.first())?;
393 let col_name = expect_str(args.get(1))?;
394 let asc = expect_bool(args.get(2))?;
395 let df = to_polars(rb)?;
396 let mut sort_opts = SortMultipleOptions::default();
397 sort_opts = sort_opts.with_order_descending(!asc);
398 let out = df.lazy()
399 .sort([col_name], sort_opts)
400 .collect()
401 .map_err(|e| format!("df.sort_by: {e}"))?;
402 pack(out)
403}
404
405fn group_by_agg(args: &[Value]) -> Result<Value, String> {
409 let rb = expect_table(args.first())?;
410 let keys_list = expect_list(args.get(1))?;
411 let specs_list = expect_list(args.get(2))?;
412
413 let mut keys: Vec<&str> = Vec::with_capacity(keys_list.len());
414 for k in keys_list {
415 let s = match k {
416 Value::Str(s) => s.as_str(),
417 other => return err(format!("group_by_agg: key list contained non-Str: {other:?}")),
418 };
419 keys.push(s);
420 }
421
422 let mut aggs: Vec<Expr> = Vec::with_capacity(specs_list.len());
423 for spec in specs_list {
424 let t = match spec {
425 Value::Tuple(t) if t.len() == 3 => t,
426 other => return err(format!(
427 "group_by_agg: spec must be (out, in, op) tuple, got {other:?}")),
428 };
429 let out_name = match &t[0] {
430 Value::Str(s) => s.as_str(),
431 other => return err(format!("group_by_agg: out_name not Str: {other:?}")),
432 };
433 let in_name = match &t[1] {
434 Value::Str(s) => s.as_str(),
435 other => return err(format!("group_by_agg: in_name not Str: {other:?}")),
436 };
437 let op = match &t[2] {
438 Value::Str(s) => s.as_str(),
439 other => return err(format!("group_by_agg: op not Str: {other:?}")),
440 };
441 let e = match op {
442 "sum" => col(in_name).sum().alias(out_name),
443 "mean" => col(in_name).mean().alias(out_name),
444 "min" => col(in_name).min().alias(out_name),
445 "max" => col(in_name).max().alias(out_name),
446 "count" => col(in_name).count().alias(out_name),
447 "n_distinct" => col(in_name).n_unique().alias(out_name),
448 other => return err(format!(
449 "group_by_agg: unknown op `{other}` (v1: sum|mean|min|max|count|n_distinct)")),
450 };
451 aggs.push(e);
452 }
453
454 let df = to_polars(rb)?;
455 let out = df.lazy()
456 .group_by(keys.iter().map(|k| col(*k)).collect::<Vec<_>>())
457 .agg(aggs)
458 .collect()
459 .map_err(|e| format!("df.group_by_agg: {e}"))?;
460 pack(out)
461}
462
463fn inner_join(args: &[Value]) -> Result<Value, String> {
464 let lhs = expect_table(args.first())?;
465 let rhs = expect_table(args.get(1))?;
466 let on = expect_str(args.get(2))?;
467 let l = to_polars(lhs)?;
468 let r = to_polars(rhs)?;
469 let out = l.lazy()
470 .join(r.lazy(), [col(on)], [col(on)], JoinArgs::new(JoinType::Inner))
471 .collect()
472 .map_err(|e| format!("df.inner_join: {e}"))?;
473 pack(out)
474}
475
476fn left_join(args: &[Value]) -> Result<Value, String> {
477 let lhs = expect_table(args.first())?;
478 let rhs = expect_table(args.get(1))?;
479 let on = expect_str(args.get(2))?;
480 let l = to_polars(lhs)?;
481 let r = to_polars(rhs)?;
482 let out = l.lazy()
483 .join(r.lazy(), [col(on)], [col(on)], JoinArgs::new(JoinType::Left))
484 .collect()
485 .map_err(|e| format!("df.left_join: {e}"))?;
486 pack(out)
487}
488
489fn ok(v: Value) -> Value {
492 Value::Variant { name: "Ok".into(), args: vec![v] }
493}
494
495fn err_variant(s: String) -> Value {
496 Value::Variant { name: "Err".into(), args: vec![Value::Str(s.into())] }
497}
498
499fn lift_result(r: Result<Value, String>) -> Result<Value, String> {
500 match r {
501 Ok(v) => Ok(ok(v)),
502 Err(s) => Ok(err_variant(s)),
503 }
504}
505
506pub fn dispatch(op: &str, args: &[Value]) -> Option<Result<Value, String>> {
509 Some(match op {
510 "filter_eq_int" => lift_result(filter_eq_int(args)),
511 "filter_gt_int" => lift_result(filter_gt_int(args)),
512 "filter_lt_int" => lift_result(filter_lt_int(args)),
513 "filter_eq_str" => lift_result(filter_eq_str(args)),
515 "filter_in_str" => lift_result(filter_in_str(args)),
516 "filter_eq_float" => lift_result(filter_eq_float(args)),
517 "filter_lt_float" => lift_result(filter_lt_float(args)),
518 "filter_gt_float" => lift_result(filter_gt_float(args)),
519 "filter_isnull" => lift_result(filter_isnull(args)),
520 "filter_notnull" => lift_result(filter_notnull(args)),
521 "drop_nulls" => lift_result(drop_nulls(args)),
522 "sort_by" => lift_result(sort_by(args)),
523 "group_by_agg" => lift_result(group_by_agg(args)),
524 "inner_join" => lift_result(inner_join(args)),
525 "left_join" => lift_result(left_join(args)),
526 _ => return None,
527 })
528}