1use crate::data::datatable::DataValue;
2use crate::sql::functions::{ArgCount, FunctionCategory, FunctionSignature, SqlFunction};
3use anyhow::{anyhow, Result};
4
5pub struct RenderNumberFunction;
7
8impl SqlFunction for RenderNumberFunction {
9 fn signature(&self) -> FunctionSignature {
10 FunctionSignature {
11 name: "RENDER_NUMBER",
12 category: FunctionCategory::String,
13 arg_count: ArgCount::Range(1, 3),
14 description: "Format numbers with separators, abbreviations, or regional formats",
15 returns: "STRING",
16 examples: vec![
17 "SELECT RENDER_NUMBER(1234567.89)", "SELECT RENDER_NUMBER(1234567.89, 'compact')", "SELECT RENDER_NUMBER(1234.56, 'eu')", "SELECT RENDER_NUMBER(-1234.56, 'accounting')", "SELECT RENDER_NUMBER(1500000, 'compact', 1)", ],
23 }
24 }
25
26 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
27 self.validate_args(args)?;
28
29 let value = match &args[0] {
30 DataValue::Integer(n) => *n as f64,
31 DataValue::Float(f) => *f,
32 DataValue::Null => return Ok(DataValue::Null),
33 _ => return Err(anyhow!("RENDER_NUMBER requires a numeric value")),
34 };
35
36 let format = if args.len() >= 2 {
37 match &args[1] {
38 DataValue::String(s) => s.to_lowercase(),
39 DataValue::Null => "standard".to_string(),
40 _ => "standard".to_string(),
41 }
42 } else {
43 "standard".to_string()
44 };
45
46 let decimals = if args.len() >= 3 {
47 match &args[2] {
48 DataValue::Integer(n) => *n as usize,
49 DataValue::Float(f) => *f as usize,
50 _ => 2,
51 }
52 } else {
53 2
54 };
55
56 let formatted = match format.as_str() {
57 "compact" => format_compact(value, decimals),
58 "eu" | "european" => format_european(value, decimals),
59 "ch" | "swiss" => format_swiss(value, decimals),
60 "in" | "indian" => format_indian(value, decimals),
61 "accounting" => format_accounting(value, decimals),
62 _ => format_standard(value, decimals), };
64
65 Ok(DataValue::String(formatted))
66 }
67}
68
69pub struct FormatCurrencyFunction;
71
72impl SqlFunction for FormatCurrencyFunction {
73 fn signature(&self) -> FunctionSignature {
74 FunctionSignature {
75 name: "FORMAT_CURRENCY",
76 category: FunctionCategory::String,
77 arg_count: ArgCount::Range(2, 4),
78 description: "Format numbers as currency with symbols, codes, or names. Currency can be from a column.",
79 returns: "STRING",
80 examples: vec![
81 "SELECT FORMAT_CURRENCY(1234.56, 'USD')", "SELECT FORMAT_CURRENCY(1234.56, 'GBP', 'symbol')", "SELECT FORMAT_CURRENCY(amount, currency_code)", "SELECT FORMAT_CURRENCY(3000, 'GBP', 'compact_code')", "SELECT FORMAT_CURRENCY(1234.56, 'EUR', 'eu')", ],
87 }
88 }
89
90 fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
91 self.validate_args(args)?;
92
93 let value = match &args[0] {
94 DataValue::Integer(n) => *n as f64,
95 DataValue::Float(f) => *f,
96 DataValue::Null => return Ok(DataValue::Null),
97 _ => return Err(anyhow!("FORMAT_CURRENCY requires a numeric value")),
98 };
99
100 let currency_code = match &args[1] {
101 DataValue::String(s) => s.to_uppercase(),
102 DataValue::Null => return Ok(DataValue::Null),
103 _ => {
104 return Err(anyhow!(
105 "FORMAT_CURRENCY requires a currency code (e.g., 'USD', 'EUR', 'GBP')"
106 ))
107 }
108 };
109
110 let format = if args.len() >= 3 {
111 match &args[2] {
112 DataValue::String(s) => s.to_lowercase(),
113 DataValue::Null => "symbol".to_string(),
114 _ => "symbol".to_string(),
115 }
116 } else {
117 "symbol".to_string()
118 };
119
120 let decimals_override = if args.len() >= 4 {
121 match &args[3] {
122 DataValue::Integer(n) => Some(*n as usize),
123 DataValue::Float(f) => Some(*f as usize),
124 _ => None,
125 }
126 } else {
127 None
128 };
129
130 let currency_info = get_currency_info(¤cy_code);
131 let decimals = decimals_override.unwrap_or(currency_info.decimals as usize);
132
133 let formatted = format_currency(value, ¤cy_code, &format, decimals, ¤cy_info);
134
135 Ok(DataValue::String(formatted))
136 }
137}
138
139struct CurrencyInfo {
141 symbol: &'static str,
142 name: &'static str,
143 decimals: u8,
144 symbol_before: bool,
145}
146
147fn get_currency_info(code: &str) -> CurrencyInfo {
149 match code {
150 "USD" => CurrencyInfo {
151 symbol: "$",
152 name: "US dollar",
153 decimals: 2,
154 symbol_before: true,
155 },
156 "EUR" => CurrencyInfo {
157 symbol: "€",
158 name: "Euro",
159 decimals: 2,
160 symbol_before: true,
161 },
162 "GBP" => CurrencyInfo {
163 symbol: "£",
164 name: "British pound",
165 decimals: 2,
166 symbol_before: true,
167 },
168 "JPY" | "YEN" => CurrencyInfo {
169 symbol: "¥",
170 name: "Japanese yen",
171 decimals: 0,
172 symbol_before: true,
173 },
174 "CHF" => CurrencyInfo {
175 symbol: "CHF",
176 name: "Swiss franc",
177 decimals: 2,
178 symbol_before: true,
179 },
180 "CNY" | "RMB" => CurrencyInfo {
181 symbol: "¥",
182 name: "Chinese yuan",
183 decimals: 2,
184 symbol_before: true,
185 },
186 "INR" => CurrencyInfo {
187 symbol: "₹",
188 name: "Indian rupee",
189 decimals: 2,
190 symbol_before: true,
191 },
192 "AUD" => CurrencyInfo {
193 symbol: "A$",
194 name: "Australian dollar",
195 decimals: 2,
196 symbol_before: true,
197 },
198 "CAD" => CurrencyInfo {
199 symbol: "C$",
200 name: "Canadian dollar",
201 decimals: 2,
202 symbol_before: true,
203 },
204 "SEK" => CurrencyInfo {
205 symbol: "kr",
206 name: "Swedish krona",
207 decimals: 2,
208 symbol_before: false,
209 },
210 "NOK" => CurrencyInfo {
211 symbol: "kr",
212 name: "Norwegian krone",
213 decimals: 2,
214 symbol_before: false,
215 },
216 "DKK" => CurrencyInfo {
217 symbol: "kr",
218 name: "Danish krone",
219 decimals: 2,
220 symbol_before: false,
221 },
222 _ => CurrencyInfo {
223 symbol: "¤", name: "Unknown currency",
225 decimals: 2,
226 symbol_before: false,
227 },
228 }
229}
230
231fn format_currency(
233 value: f64,
234 code: &str,
235 format: &str,
236 decimals: usize,
237 info: &CurrencyInfo,
238) -> String {
239 let is_negative = value < 0.0;
240 let abs_value = value.abs();
241
242 match format {
243 "accounting" => {
244 let formatted = format_standard(abs_value, decimals);
246 if info.symbol_before {
247 if is_negative {
248 format!("({}{})", info.symbol, formatted)
249 } else {
250 format!("{}{}", info.symbol, formatted)
251 }
252 } else {
253 if is_negative {
254 format!("({}{})", formatted, info.symbol)
255 } else {
256 format!("{}{}", formatted, info.symbol)
257 }
258 }
259 }
260 "accounting_code" => {
261 let formatted = format_standard(abs_value, decimals);
263 if is_negative {
264 format!("({} {})", formatted, code)
265 } else {
266 format!("{} {}", formatted, code)
267 }
268 }
269 "compact" => {
270 let compact = format_compact(abs_value, decimals.min(1));
271 if info.symbol_before {
272 format!(
273 "{}{}{}",
274 if is_negative { "-" } else { "" },
275 info.symbol,
276 compact
277 )
278 } else {
279 format!(
280 "{}{}{}",
281 if is_negative { "-" } else { "" },
282 compact,
283 info.symbol
284 )
285 }
286 }
287 "compact_code" => {
288 let compact = format_compact(abs_value, decimals.min(1));
289 format!("{}{} {}", if is_negative { "-" } else { "" }, compact, code)
290 }
291 "code" => {
292 let formatted = format_standard(abs_value, decimals);
293 format!(
294 "{}{} {}",
295 if is_negative { "-" } else { "" },
296 formatted,
297 code
298 )
299 }
300 "name" => {
301 let formatted = format_standard(abs_value, decimals);
302 let plural = if abs_value != 1.0 { "s" } else { "" };
303 format!(
304 "{}{} {}{}",
305 if is_negative { "-" } else { "" },
306 formatted,
307 info.name,
308 plural
309 )
310 }
311 "eu" | "european" => {
312 let formatted = format_european(abs_value, decimals);
313 if code == "EUR" {
314 format!("{}{} €", if is_negative { "-" } else { "" }, formatted)
315 } else {
316 format!(
317 "{}{} {}",
318 if is_negative { "-" } else { "" },
319 formatted,
320 code
321 )
322 }
323 }
324 "ch" | "swiss" => {
325 let formatted = format_swiss(abs_value, decimals);
326 format!(
327 "{}{} {}",
328 if is_negative { "-" } else { "" },
329 code,
330 formatted
331 )
332 }
333 _ => {
334 let formatted = format_standard(abs_value, decimals);
336 if info.symbol_before {
337 format!(
338 "{}{}{}",
339 if is_negative { "-" } else { "" },
340 info.symbol,
341 formatted
342 )
343 } else {
344 format!(
345 "{}{}{}",
346 if is_negative { "-" } else { "" },
347 formatted,
348 info.symbol
349 )
350 }
351 }
352 }
353}
354
355fn format_standard(value: f64, decimals: usize) -> String {
357 let formatted = format!("{:.prec$}", value, prec = decimals);
358 add_separators(&formatted, ',', '.')
359}
360
361fn format_european(value: f64, decimals: usize) -> String {
363 let formatted = format!("{:.prec$}", value, prec = decimals);
364 add_separators(&formatted, '.', ',')
365}
366
367fn format_swiss(value: f64, decimals: usize) -> String {
369 let formatted = format!("{:.prec$}", value, prec = decimals);
370 add_separators(&formatted, '\'', '.')
371}
372
373fn format_indian(value: f64, decimals: usize) -> String {
375 let formatted = format!("{:.prec$}", value, prec = decimals);
376 add_indian_separators(&formatted)
377}
378
379fn format_accounting(value: f64, decimals: usize) -> String {
381 let is_negative = value < 0.0;
382 let abs_value = value.abs();
383 let formatted = format!("{:.prec$}", abs_value, prec = decimals);
384 let with_separators = add_separators(&formatted, ',', '.');
385
386 if is_negative {
387 format!("({})", with_separators)
388 } else {
389 with_separators
390 }
391}
392
393fn format_compact(value: f64, decimals: usize) -> String {
395 let (num, suffix) = if value >= 1_000_000_000_000.0 {
396 (value / 1_000_000_000_000.0, "T")
397 } else if value >= 1_000_000_000.0 {
398 (value / 1_000_000_000.0, "B")
399 } else if value >= 1_000_000.0 {
400 (value / 1_000_000.0, "M")
401 } else if value >= 1_000.0 {
402 (value / 1_000.0, "k")
403 } else {
404 (value, "")
405 };
406
407 if suffix.is_empty() {
408 format!("{:.prec$}", num, prec = decimals)
409 } else {
410 let formatted = format!("{:.prec$}", num, prec = decimals);
412 let trimmed = formatted.trim_end_matches('0').trim_end_matches('.');
413 format!("{}{}", trimmed, suffix)
414 }
415}
416
417fn add_separators(formatted: &str, thousand_sep: char, decimal_sep: char) -> String {
419 let parts: Vec<&str> = formatted.split('.').collect();
420 let integer_part = parts[0];
421 let decimal_part = parts.get(1);
422
423 let mut result = String::new();
424 let mut count = 0;
425
426 for ch in integer_part.chars().rev() {
427 if count == 3 {
428 result.push(thousand_sep);
429 count = 0;
430 }
431 result.push(ch);
432 count += 1;
433 }
434
435 let mut final_result: String = result.chars().rev().collect();
436
437 if let Some(dec) = decimal_part {
438 final_result.push(decimal_sep);
439 final_result.push_str(dec);
440 }
441
442 final_result
443}
444
445fn add_indian_separators(formatted: &str) -> String {
447 let parts: Vec<&str> = formatted.split('.').collect();
448 let integer_part = parts[0];
449 let decimal_part = parts.get(1);
450
451 let mut result = String::new();
452 let chars: Vec<char> = integer_part.chars().rev().collect();
453
454 for (i, ch) in chars.iter().enumerate() {
455 if i == 3 || (i > 3 && i % 2 == 1) {
456 result.push(',');
457 }
458 result.push(*ch);
459 }
460
461 let mut final_result: String = result.chars().rev().collect();
462
463 if let Some(dec) = decimal_part {
464 final_result.push('.');
465 final_result.push_str(dec);
466 }
467
468 final_result
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474
475 #[test]
476 fn test_render_number_standard() {
477 let func = RenderNumberFunction;
478
479 let result = func.evaluate(&[DataValue::Float(1234567.89)]).unwrap();
480 assert_eq!(result, DataValue::String("1,234,567.89".to_string()));
481
482 let result = func.evaluate(&[DataValue::Integer(1000)]).unwrap();
483 assert_eq!(result, DataValue::String("1,000.00".to_string()));
484 }
485
486 #[test]
487 fn test_render_number_compact() {
488 let func = RenderNumberFunction;
489
490 let result = func
491 .evaluate(&[
492 DataValue::Float(1500000.0),
493 DataValue::String("compact".to_string()),
494 ])
495 .unwrap();
496 assert_eq!(result, DataValue::String("1.5M".to_string()));
497
498 let result = func
499 .evaluate(&[
500 DataValue::Integer(3000),
501 DataValue::String("compact".to_string()),
502 ])
503 .unwrap();
504 assert_eq!(result, DataValue::String("3k".to_string()));
505 }
506
507 #[test]
508 fn test_format_currency() {
509 let func = FormatCurrencyFunction;
510
511 let result = func
512 .evaluate(&[
513 DataValue::Float(1234.56),
514 DataValue::String("USD".to_string()),
515 ])
516 .unwrap();
517 assert_eq!(result, DataValue::String("$1,234.56".to_string()));
518
519 let result = func
520 .evaluate(&[
521 DataValue::Float(1234.56),
522 DataValue::String("GBP".to_string()),
523 ])
524 .unwrap();
525 assert_eq!(result, DataValue::String("£1,234.56".to_string()));
526
527 let result = func
528 .evaluate(&[
529 DataValue::Integer(3000),
530 DataValue::String("GBP".to_string()),
531 DataValue::String("compact_code".to_string()),
532 ])
533 .unwrap();
534 assert_eq!(result, DataValue::String("3k GBP".to_string()));
535 }
536}