|
37 | 37 | import bigframes.operations.generic_ops as gen_ops |
38 | 38 | import bigframes.operations.json_ops as json_ops |
39 | 39 | import bigframes.operations.numeric_ops as num_ops |
| 40 | +import bigframes.operations.remote_function_ops as remote_function_ops |
40 | 41 | import bigframes.operations.string_ops as string_ops |
| 42 | +import bigframes.operations.struct_ops as struct_ops |
41 | 43 | from bigframes.core import agg_expressions, identifiers, nodes, ordering, window_spec |
42 | 44 | from bigframes.core.compile.polars import lowering |
43 | 45 |
|
@@ -122,7 +124,7 @@ def _bigframes_dtype_to_polars_dtype( |
122 | 124 | ] |
123 | 125 | ) |
124 | 126 | if bigframes.dtypes.is_array_like(dtype): |
125 | | - return pl.Array( |
| 127 | + return pl.List( |
126 | 128 | inner=_bigframes_dtype_to_polars_dtype( |
127 | 129 | bigframes.dtypes.get_array_inner_type(dtype) |
128 | 130 | ) |
@@ -502,6 +504,50 @@ def _(self, op: json_ops.ToJSON, input: pl.Expr) -> pl.Expr: |
502 | 504 | else: |
503 | 505 | return input.cast(pl.String()) |
504 | 506 |
|
| 507 | + @compile_op.register(json_ops.ToJSONString) |
| 508 | + def _(self, op: json_ops.ToJSONString, input: pl.Expr) -> pl.Expr: |
| 509 | + from_type = self._expr_types.get(id(input)) |
| 510 | + |
| 511 | + def preprocess_binary( |
| 512 | + expr: pl.Expr, dtype: bigframes.dtypes.ExpressionType |
| 513 | + ) -> pl.Expr: |
| 514 | + if dtype == bigframes.dtypes.BYTES_DTYPE: |
| 515 | + return expr.bin.encode("base64") |
| 516 | + if bigframes.dtypes.is_struct_like(dtype): |
| 517 | + fields = bigframes.dtypes.get_struct_fields(dtype) |
| 518 | + return pl.struct( |
| 519 | + *[ |
| 520 | + preprocess_binary( |
| 521 | + expr.struct.field(name), field_type |
| 522 | + ).alias(name) |
| 523 | + for name, field_type in fields.items() |
| 524 | + ] |
| 525 | + ) |
| 526 | + if bigframes.dtypes.is_array_like(dtype): |
| 527 | + inner_type = bigframes.dtypes.get_array_inner_type(dtype) |
| 528 | + return expr.list.eval(preprocess_binary(pl.element(), inner_type)) |
| 529 | + return expr |
| 530 | + |
| 531 | + preprocessed = preprocess_binary(input, from_type) |
| 532 | + |
| 533 | + if bigframes.dtypes.is_struct_like(from_type): |
| 534 | + result = preprocessed.struct.json_encode() |
| 535 | + elif from_type == bigframes.dtypes.INT_DTYPE: |
| 536 | + result = preprocessed.cast(pl.String) |
| 537 | + elif from_type == bigframes.dtypes.BOOL_DTYPE: |
| 538 | + result = ( |
| 539 | + pl.when(preprocessed) |
| 540 | + .then(pl.lit("true")) |
| 541 | + .otherwise(pl.lit("false")) |
| 542 | + ) |
| 543 | + elif from_type == bigframes.dtypes.BYTES_DTYPE: |
| 544 | + result = pl.lit('"') + preprocessed + pl.lit('"') |
| 545 | + else: |
| 546 | + wrapped = pl.struct(value=preprocessed).struct.json_encode() |
| 547 | + result = wrapped.str.slice(9, wrapped.str.len_chars() - 10) |
| 548 | + |
| 549 | + return pl.when(input.is_null()).then(pl.lit("null")).otherwise(result) |
| 550 | + |
505 | 551 | @compile_op.register(arr_ops.ToArrayOp) |
506 | 552 | def _(self, op: ops.ToArrayOp, *inputs: pl.Expr) -> pl.Expr: |
507 | 553 | return pl.concat_list(*inputs) |
@@ -532,6 +578,36 @@ def _(self, op: ops.ArrayReduceOp, input: pl.Expr) -> pl.Expr: |
532 | 578 | f"Haven't implemented array aggregation: {op.aggregation}" |
533 | 579 | ) |
534 | 580 |
|
| 581 | + @compile_op.register(struct_ops.StructOp) |
| 582 | + def _(self, op: struct_ops.StructOp, *inputs: pl.Expr) -> pl.Expr: |
| 583 | + return pl.struct(**{col: inp for col, inp in zip(op.column_names, inputs)}) # type: ignore |
| 584 | + |
| 585 | + @compile_op.register(struct_ops.StructFieldOp) |
| 586 | + def _(self, op: struct_ops.StructFieldOp, *inputs: pl.Expr) -> pl.Expr: |
| 587 | + return inputs[0].struct[op.name_or_index] |
| 588 | + |
| 589 | + @compile_op.register(remote_function_ops.PythonUdfOp) |
| 590 | + def _(self, op: ops.PythonUdfOp, *inputs: pl.Expr) -> pl.Expr: |
| 591 | + from bigframes.functions import function_template |
| 592 | + |
| 593 | + code = op.function_def.code.to_callable() |
| 594 | + if op.function_def.signature.is_row_processor: |
| 595 | + |
| 596 | + def handler(py_struct): |
| 597 | + args = list(py_struct.values()) |
| 598 | + series_arg = function_template.get_pd_series(args[0]) |
| 599 | + return code(series_arg, *args[1:]) |
| 600 | + else: |
| 601 | + |
| 602 | + def handler(py_struct): |
| 603 | + return code(*(field for field in py_struct.values())) |
| 604 | + |
| 605 | + return pl.struct(*inputs).map_elements( |
| 606 | + handler, |
| 607 | + return_dtype=_bigframes_dtype_to_polars_dtype(op.output_type()), |
| 608 | + skip_nulls=False, |
| 609 | + ) |
| 610 | + |
535 | 611 | @dataclasses.dataclass(frozen=True) |
536 | 612 | class PolarsAggregateCompiler: |
537 | 613 | scalar_compiler = PolarsExpressionCompiler() |
|
0 commit comments