1616 from sidemantic .sql .generator import SQLGenerator
1717
1818
19- def _table_to_ipc (table ) -> str :
20- """Serialize Arrow table to IPC format (base64 for widget transport)."""
19+ def _table_to_ipc (table , * , decimal_mode : str = "float" ) -> str :
20+ """Serialize Arrow table to IPC format (base64 for widget transport).
21+
22+ Args:
23+ table: PyArrow table
24+ decimal_mode: "float" to cast decimals to float64, "string" to preserve precision as strings
25+ """
2126 import base64
2227
2328 import pyarrow as pa
29+ import pyarrow .compute as pc
30+
31+ if any (pa .types .is_decimal (field .type ) for field in table .schema ):
32+ arrays = []
33+ fields = []
34+ for field in table .schema :
35+ column = table [field .name ]
36+ if pa .types .is_decimal (field .type ):
37+ if decimal_mode == "string" :
38+ cast_type = pa .string ()
39+ else :
40+ cast_type = pa .float64 ()
41+ arrays .append (pc .cast (column , cast_type ))
42+ fields .append (pa .field (field .name , cast_type ))
43+ else :
44+ arrays .append (column )
45+ fields .append (field )
46+ table = pa .table (arrays , schema = pa .schema (fields ))
2447
2548 sink = io .BytesIO ()
2649 with pa .ipc .new_file (sink , table .schema ) as writer :
@@ -590,7 +613,7 @@ def _refresh_metrics(self, *, sync_status: bool = True):
590613 skip_default_time_dimensions = True ,
591614 )
592615 result = self ._conn .execute (sql ).arrow ()
593- self .metric_series_data = _table_to_ipc (result )
616+ self .metric_series_data = _table_to_ipc (result , decimal_mode = "float" )
594617
595618 totals_sql = self ._generator .generate (
596619 metrics = metric_refs ,
@@ -603,7 +626,15 @@ def _refresh_metrics(self, *, sync_status: bool = True):
603626 totals = {}
604627 if totals_row :
605628 for i , metric_ref in enumerate (metric_refs ):
606- totals [metric_ref .split ("." )[- 1 ]] = totals_row [i ]
629+ value = totals_row [i ]
630+ try :
631+ from decimal import Decimal
632+
633+ if isinstance (value , Decimal ):
634+ value = str (value )
635+ except Exception :
636+ pass
637+ totals [metric_ref .split ("." )[- 1 ]] = value
607638 self .metric_totals = totals
608639 self ._record_query_intent (metric_refs , [time_dim_ref ], grain )
609640 except Exception as e :
@@ -646,7 +677,7 @@ def _refresh_dimensions(self, *, sync_status: bool = True):
646677 use_preaggregations = self ._use_preaggregations ,
647678 )
648679 result = self ._conn .execute (sql ).arrow ()
649- dimension_data [dim_key ] = _table_to_ipc (result )
680+ dimension_data [dim_key ] = _table_to_ipc (result , decimal_mode = "string" )
650681 self .dimension_data = dict (dimension_data )
651682 self ._record_query_intent ([selected_metric_ref ], [dim_ref ], self .time_grain or "day" )
652683 except Exception as e :
@@ -680,7 +711,7 @@ def _refresh_dimensions(self, *, sync_status: bool = True):
680711 use_preaggregations = self ._use_preaggregations ,
681712 )
682713 result = self ._conn .execute (sql ).arrow ()
683- dimension_data [dim_key ] = _table_to_ipc (result )
714+ dimension_data [dim_key ] = _table_to_ipc (result , decimal_mode = "string" )
684715 self .dimension_data = dict (dimension_data )
685716 self ._record_query_intent ([selected_metric_ref ], [dim_ref ], self .time_grain or "day" )
686717 except Exception as e :
0 commit comments