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 arr: StringArray = s.str()
164 .map_err(|e| format!("df: column `{name}` as Utf8: {e}"))?
165 .iter().collect();
166 (
167 Field::new(name, ArrowDt::Utf8, true),
168 Arc::new(arr),
169 )
170 }
171 PlDt::UInt32 => {
174 let v: Vec<Option<i64>> = s.u32()
175 .map_err(|e| format!("df: column `{name}` as u32: {e}"))?
176 .iter().map(|x| x.map(|n| n as i64)).collect();
177 (
178 Field::new(name, ArrowDt::Int64, true),
179 Arc::new(Int64Array::from(v)),
180 )
181 }
182 other => return err(format!(
183 "df: polars column `{name}` has unsupported type {other:?}")),
184 };
185 fields.push(field);
186 arrays.push(array);
187 }
188 let schema = Arc::new(Schema::new(fields));
189 RecordBatch::try_new(schema, arrays)
190 .map_err(|e| format!("df: RecordBatch::try_new: {e}"))
191}
192
193fn pack(df: DataFrame) -> Result<Value, String> {
196 let rb = from_polars(&df)?;
197 Ok(Value::ArrowTable(Arc::new(rb)))
198}
199
200fn filter_eq_int(args: &[Value]) -> Result<Value, String> {
201 let rb = expect_table(args.first())?;
202 let col_name = expect_str(args.get(1))?;
203 let needle = expect_int(args.get(2))?;
204 let df = to_polars(rb)?;
205 let out = df.lazy()
206 .filter(col(col_name).eq(lit(needle)))
207 .collect()
208 .map_err(|e| format!("df.filter_eq_int: {e}"))?;
209 pack(out)
210}
211
212fn filter_gt_int(args: &[Value]) -> Result<Value, String> {
213 let rb = expect_table(args.first())?;
214 let col_name = expect_str(args.get(1))?;
215 let needle = expect_int(args.get(2))?;
216 let df = to_polars(rb)?;
217 let out = df.lazy()
218 .filter(col(col_name).gt(lit(needle)))
219 .collect()
220 .map_err(|e| format!("df.filter_gt_int: {e}"))?;
221 pack(out)
222}
223
224fn filter_lt_int(args: &[Value]) -> Result<Value, String> {
225 let rb = expect_table(args.first())?;
226 let col_name = expect_str(args.get(1))?;
227 let needle = expect_int(args.get(2))?;
228 let df = to_polars(rb)?;
229 let out = df.lazy()
230 .filter(col(col_name).lt(lit(needle)))
231 .collect()
232 .map_err(|e| format!("df.filter_lt_int: {e}"))?;
233 pack(out)
234}
235
236fn expect_col_type(rb: &RecordBatch, col_name: &str, wanted: ArrowDt, op: &str) -> Result<(), String> {
243 let schema = rb.schema();
244 let (_, field) = schema
245 .column_with_name(col_name)
246 .ok_or_else(|| format!("df.{op}: column `{col_name}` not found"))?;
247 if field.data_type() != &wanted {
248 return err(format!(
249 "df.{op}: expected {wanted:?} column, got {:?}",
250 field.data_type()
251 ));
252 }
253 Ok(())
254}
255
256fn filter_eq_str(args: &[Value]) -> Result<Value, String> {
257 let rb = expect_table(args.first())?;
258 let col_name = expect_str(args.get(1))?;
259 let needle = expect_str(args.get(2))?;
260 expect_col_type(rb, col_name, ArrowDt::Utf8, "filter_eq_str")?;
261 let df = to_polars(rb)?;
262 let out = df.lazy()
263 .filter(col(col_name).eq(lit(needle.to_string())))
264 .collect()
265 .map_err(|e| format!("df.filter_eq_str: {e}"))?;
266 pack(out)
267}
268
269fn filter_in_str(args: &[Value]) -> Result<Value, String> {
270 let rb = expect_table(args.first())?;
271 let col_name = expect_str(args.get(1))?;
272 let needles_list = expect_list(args.get(2))?;
273 expect_col_type(rb, col_name, ArrowDt::Utf8, "filter_in_str")?;
274 let mut needles: Vec<String> = Vec::with_capacity(needles_list.len());
275 for v in needles_list {
276 match v {
277 Value::Str(s) => needles.push(s.to_string()),
278 other => return err(format!(
279 "df.filter_in_str: needle list contained non-Str: {other:?}")),
280 }
281 }
282 if needles.is_empty() {
284 let empty = RecordBatch::new_empty(rb.schema());
287 return Ok(Value::ArrowTable(Arc::new(empty)));
288 }
289 let df = to_polars(rb)?;
290 let needle_series: Series =
291 Series::new(PlSmallStr::from_static("__in"), needles).into_column().take_materialized_series();
292 let out = df.lazy()
293 .filter(col(col_name).is_in(lit(needle_series), false))
294 .collect()
295 .map_err(|e| format!("df.filter_in_str: {e}"))?;
296 pack(out)
297}
298
299fn filter_eq_float(args: &[Value]) -> Result<Value, String> {
300 let rb = expect_table(args.first())?;
301 let col_name = expect_str(args.get(1))?;
302 let needle = expect_float(args.get(2))?;
303 expect_col_type(rb, col_name, ArrowDt::Float64, "filter_eq_float")?;
304 let df = to_polars(rb)?;
305 let out = df.lazy()
306 .filter(col(col_name).eq(lit(needle)))
307 .collect()
308 .map_err(|e| format!("df.filter_eq_float: {e}"))?;
309 pack(out)
310}
311
312fn filter_lt_float(args: &[Value]) -> Result<Value, String> {
313 let rb = expect_table(args.first())?;
314 let col_name = expect_str(args.get(1))?;
315 let needle = expect_float(args.get(2))?;
316 expect_col_type(rb, col_name, ArrowDt::Float64, "filter_lt_float")?;
317 let df = to_polars(rb)?;
318 let out = df.lazy()
319 .filter(col(col_name).lt(lit(needle)))
320 .collect()
321 .map_err(|e| format!("df.filter_lt_float: {e}"))?;
322 pack(out)
323}
324
325fn filter_gt_float(args: &[Value]) -> Result<Value, String> {
326 let rb = expect_table(args.first())?;
327 let col_name = expect_str(args.get(1))?;
328 let needle = expect_float(args.get(2))?;
329 expect_col_type(rb, col_name, ArrowDt::Float64, "filter_gt_float")?;
330 let df = to_polars(rb)?;
331 let out = df.lazy()
332 .filter(col(col_name).gt(lit(needle)))
333 .collect()
334 .map_err(|e| format!("df.filter_gt_float: {e}"))?;
335 pack(out)
336}
337
338fn filter_isnull(args: &[Value]) -> Result<Value, String> {
339 let rb = expect_table(args.first())?;
340 let col_name = expect_str(args.get(1))?;
341 if rb.schema().column_with_name(col_name).is_none() {
343 return err(format!("df.filter_isnull: column `{col_name}` not found"));
344 }
345 let df = to_polars(rb)?;
346 let out = df.lazy()
347 .filter(col(col_name).is_null())
348 .collect()
349 .map_err(|e| format!("df.filter_isnull: {e}"))?;
350 pack(out)
351}
352
353fn filter_notnull(args: &[Value]) -> Result<Value, String> {
354 let rb = expect_table(args.first())?;
355 let col_name = expect_str(args.get(1))?;
356 if rb.schema().column_with_name(col_name).is_none() {
357 return err(format!("df.filter_notnull: column `{col_name}` not found"));
358 }
359 let df = to_polars(rb)?;
360 let out = df.lazy()
361 .filter(col(col_name).is_not_null())
362 .collect()
363 .map_err(|e| format!("df.filter_notnull: {e}"))?;
364 pack(out)
365}
366
367fn drop_nulls(args: &[Value]) -> Result<Value, String> {
368 let rb = expect_table(args.first())?;
369 let cols_list = expect_list(args.get(1))?;
370 if cols_list.is_empty() {
372 return Ok(Value::ArrowTable(Arc::clone(rb)));
373 }
374 let mut cols: Vec<String> = Vec::with_capacity(cols_list.len());
375 {
376 let schema = rb.schema();
377 for v in cols_list {
378 match v {
379 Value::Str(s) => {
380 if schema.column_with_name(s.as_str()).is_none() {
381 return err(format!("df.drop_nulls: column `{s}` not found"));
382 }
383 cols.push(s.to_string());
384 }
385 other => return err(format!(
386 "df.drop_nulls: column list contained non-Str: {other:?}")),
387 }
388 }
389 }
390 let df = to_polars(rb)?;
391 let out = df
392 .drop_nulls(Some(&cols))
393 .map_err(|e| format!("df.drop_nulls: {e}"))?;
394 pack(out)
395}
396
397fn sort_by(args: &[Value]) -> Result<Value, String> {
398 let rb = expect_table(args.first())?;
399 let col_name = expect_str(args.get(1))?;
400 let asc = expect_bool(args.get(2))?;
401 let df = to_polars(rb)?;
402 let mut sort_opts = SortMultipleOptions::default();
403 sort_opts = sort_opts.with_order_descending(!asc);
404 let out = df.lazy()
405 .sort([col_name], sort_opts)
406 .collect()
407 .map_err(|e| format!("df.sort_by: {e}"))?;
408 pack(out)
409}
410
411fn group_by_agg(args: &[Value]) -> Result<Value, String> {
415 let rb = expect_table(args.first())?;
416 let keys_list = expect_list(args.get(1))?;
417 let specs_list = expect_list(args.get(2))?;
418
419 let mut keys: Vec<&str> = Vec::with_capacity(keys_list.len());
420 for k in keys_list {
421 let s = match k {
422 Value::Str(s) => s.as_str(),
423 other => return err(format!("group_by_agg: key list contained non-Str: {other:?}")),
424 };
425 keys.push(s);
426 }
427
428 let mut aggs: Vec<Expr> = Vec::with_capacity(specs_list.len());
429 for spec in specs_list {
430 let t = match spec {
431 Value::Tuple(t) if t.len() == 3 => t,
432 other => return err(format!(
433 "group_by_agg: spec must be (out, in, op) tuple, got {other:?}")),
434 };
435 let out_name = match &t[0] {
436 Value::Str(s) => s.as_str(),
437 other => return err(format!("group_by_agg: out_name not Str: {other:?}")),
438 };
439 let in_name = match &t[1] {
440 Value::Str(s) => s.as_str(),
441 other => return err(format!("group_by_agg: in_name not Str: {other:?}")),
442 };
443 let op = match &t[2] {
444 Value::Str(s) => s.as_str(),
445 other => return err(format!("group_by_agg: op not Str: {other:?}")),
446 };
447 let e = match op {
448 "sum" => col(in_name).sum().alias(out_name),
449 "mean" => col(in_name).mean().alias(out_name),
450 "min" => col(in_name).min().alias(out_name),
451 "max" => col(in_name).max().alias(out_name),
452 "count" => col(in_name).count().alias(out_name),
453 "n_distinct" => col(in_name).n_unique().alias(out_name),
454 other => return err(format!(
455 "group_by_agg: unknown op `{other}` (v1: sum|mean|min|max|count|n_distinct)")),
456 };
457 aggs.push(e);
458 }
459
460 let df = to_polars(rb)?;
461 let out = df.lazy()
462 .group_by(keys.iter().map(|k| col(*k)).collect::<Vec<_>>())
463 .agg(aggs)
464 .collect()
465 .map_err(|e| format!("df.group_by_agg: {e}"))?;
466 pack(out)
467}
468
469fn inner_join(args: &[Value]) -> Result<Value, String> {
470 let lhs = expect_table(args.first())?;
471 let rhs = expect_table(args.get(1))?;
472 let on = expect_str(args.get(2))?;
473 let l = to_polars(lhs)?;
474 let r = to_polars(rhs)?;
475 let out = l.lazy()
476 .join(r.lazy(), [col(on)], [col(on)], JoinArgs::new(JoinType::Inner))
477 .collect()
478 .map_err(|e| format!("df.inner_join: {e}"))?;
479 pack(out)
480}
481
482fn left_join(args: &[Value]) -> Result<Value, String> {
483 let lhs = expect_table(args.first())?;
484 let rhs = expect_table(args.get(1))?;
485 let on = expect_str(args.get(2))?;
486 let l = to_polars(lhs)?;
487 let r = to_polars(rhs)?;
488 let out = l.lazy()
489 .join(r.lazy(), [col(on)], [col(on)], JoinArgs::new(JoinType::Left))
490 .collect()
491 .map_err(|e| format!("df.left_join: {e}"))?;
492 pack(out)
493}
494
495fn cross_join(args: &[Value]) -> Result<Value, String> {
496 let lhs = expect_table(args.first())?;
497 let rhs = expect_table(args.get(1))?;
498 let l = to_polars(lhs)?;
499 let r = to_polars(rhs)?;
500 let out = l.lazy()
501 .cross_join(r.lazy(), None)
502 .collect()
503 .map_err(|e| format!("df.cross_join: {e}"))?;
504 pack(out)
505}
506
507pub fn read_csv_at_polars(path: &std::path::Path) -> Result<Value, String> {
526 use polars::prelude::{CsvReadOptions, SerReader};
527
528 let df = CsvReadOptions::default()
529 .with_has_header(true)
530 .with_infer_schema_length(Some(100))
531 .try_into_reader_with_file_path(Some(path.to_path_buf()))
532 .map_err(|e| format!("arrow.read_csv: open `{}`: {e}", path.display()))?
533 .finish()
534 .map_err(|e| format!("arrow.read_csv: parse `{}`: {e}", path.display()))?;
535
536 let mut casts: Vec<Expr> = Vec::new();
538 for (name, dtype) in df.schema().iter() {
539 let target = match dtype {
540 PlDt::Int64 | PlDt::Float64 | PlDt::String => continue,
541 PlDt::Int8
542 | PlDt::Int16
543 | PlDt::Int32
544 | PlDt::UInt8
545 | PlDt::UInt16
546 | PlDt::UInt32
547 | PlDt::UInt64 => PlDt::Int64,
548 PlDt::Float32 => PlDt::Float64,
549 _ => PlDt::String,
550 };
551 casts.push(col(name.as_str()).cast(target));
552 }
553 let df = if casts.is_empty() {
554 df
555 } else {
556 df.lazy()
557 .with_columns(casts)
558 .collect()
559 .map_err(|e| format!("arrow.read_csv: dtype normalisation: {e}"))?
560 };
561 pack(df)
562}
563
564fn ok(v: Value) -> Value {
567 Value::Variant { name: "Ok".into(), args: vec![v] }
568}
569
570fn err_variant(s: String) -> Value {
571 Value::Variant { name: "Err".into(), args: vec![Value::Str(s.into())] }
572}
573
574fn lift_result(r: Result<Value, String>) -> Result<Value, String> {
575 match r {
576 Ok(v) => Ok(ok(v)),
577 Err(s) => Ok(err_variant(s)),
578 }
579}
580
581pub fn dispatch(op: &str, args: &[Value]) -> Option<Result<Value, String>> {
584 Some(match op {
585 "filter_eq_int" => lift_result(filter_eq_int(args)),
586 "filter_gt_int" => lift_result(filter_gt_int(args)),
587 "filter_lt_int" => lift_result(filter_lt_int(args)),
588 "filter_eq_str" => lift_result(filter_eq_str(args)),
590 "filter_in_str" => lift_result(filter_in_str(args)),
591 "filter_eq_float" => lift_result(filter_eq_float(args)),
592 "filter_lt_float" => lift_result(filter_lt_float(args)),
593 "filter_gt_float" => lift_result(filter_gt_float(args)),
594 "filter_isnull" => lift_result(filter_isnull(args)),
595 "filter_notnull" => lift_result(filter_notnull(args)),
596 "drop_nulls" => lift_result(drop_nulls(args)),
597 "sort_by" => lift_result(sort_by(args)),
598 "group_by_agg" => lift_result(group_by_agg(args)),
599 "inner_join" => lift_result(inner_join(args)),
600 "left_join" => lift_result(left_join(args)),
601 "cross_join" => lift_result(cross_join(args)),
602 _ => return None,
603 })
604}