sql_cli/sql/functions/
base_conversion.rs

1use crate::data::datatable::DataValue;
2use crate::sql::functions::{ArgCount, FunctionCategory, FunctionSignature, SqlFunction};
3use anyhow::{anyhow, Result};
4
5/// Convert a number to a string representation in the specified base (2-36)
6pub struct ToBase;
7
8impl SqlFunction for ToBase {
9    fn signature(&self) -> FunctionSignature {
10        FunctionSignature {
11            name: "TO_BASE",
12            category: FunctionCategory::Mathematical,
13            arg_count: ArgCount::Fixed(2),
14            description: "Convert a number to a string in the specified base (2-36)",
15            returns: "String representation in the specified base",
16            examples: vec![
17                "SELECT TO_BASE(255, 16)  -- Returns 'ff'",
18                "SELECT TO_BASE(42, 2)    -- Returns '101010'",
19                "SELECT TO_BASE(100, 8)   -- Returns '144'",
20            ],
21        }
22    }
23
24    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
25        if args.len() != 2 {
26            return Err(anyhow!("TO_BASE requires exactly 2 arguments"));
27        }
28
29        let number = match &args[0] {
30            DataValue::Integer(i) => *i,
31            DataValue::Float(f) => *f as i64,
32            DataValue::Null => return Ok(DataValue::Null),
33            _ => {
34                return Err(anyhow!(
35                    "TO_BASE requires a numeric value as first argument"
36                ))
37            }
38        };
39
40        let base = match &args[1] {
41            DataValue::Integer(i) => *i as i32,
42            DataValue::Float(f) => *f as i32,
43            DataValue::Null => return Ok(DataValue::Null),
44            _ => {
45                return Err(anyhow!(
46                    "TO_BASE requires an integer base as second argument"
47                ))
48            }
49        };
50
51        if base < 2 || base > 36 {
52            return Err(anyhow!("Base must be between 2 and 36, got {}", base));
53        }
54
55        if number < 0 {
56            // Handle negative numbers by prepending '-'
57            let result = format!("-{}", to_base_string((-number) as u64, base as u32));
58            Ok(DataValue::String(result))
59        } else {
60            let result = to_base_string(number as u64, base as u32);
61            Ok(DataValue::String(result))
62        }
63    }
64}
65
66/// Convert a string representation in the specified base to a number
67pub struct FromBase;
68
69impl SqlFunction for FromBase {
70    fn signature(&self) -> FunctionSignature {
71        FunctionSignature {
72            name: "FROM_BASE",
73            category: FunctionCategory::Mathematical,
74            arg_count: ArgCount::Fixed(2),
75            description: "Convert a string in the specified base (2-36) to a number",
76            returns: "Numeric value",
77            examples: vec![
78                "SELECT FROM_BASE('ff', 16)     -- Returns 255",
79                "SELECT FROM_BASE('101010', 2)  -- Returns 42",
80                "SELECT FROM_BASE('144', 8)     -- Returns 100",
81            ],
82        }
83    }
84
85    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
86        if args.len() != 2 {
87            return Err(anyhow!("FROM_BASE requires exactly 2 arguments"));
88        }
89
90        let string = match &args[0] {
91            DataValue::String(s) => s.trim(),
92            DataValue::Null => return Ok(DataValue::Null),
93            _ => return Err(anyhow!("FROM_BASE requires a string as first argument")),
94        };
95
96        let base = match &args[1] {
97            DataValue::Integer(i) => *i as u32,
98            DataValue::Float(f) => *f as u32,
99            DataValue::Null => return Ok(DataValue::Null),
100            _ => {
101                return Err(anyhow!(
102                    "FROM_BASE requires an integer base as second argument"
103                ))
104            }
105        };
106
107        if base < 2 || base > 36 {
108            return Err(anyhow!("Base must be between 2 and 36, got {}", base));
109        }
110
111        // Handle negative numbers
112        let (is_negative, string) = if string.starts_with('-') {
113            (true, &string[1..])
114        } else {
115            (false, string)
116        };
117
118        match i64::from_str_radix(string, base) {
119            Ok(n) => {
120                let result = if is_negative { -n } else { n };
121                Ok(DataValue::Integer(result))
122            }
123            Err(e) => Err(anyhow!("Invalid {} base number '{}': {}", base, string, e)),
124        }
125    }
126}
127
128/// Convert to binary string
129pub struct ToBinary;
130
131impl SqlFunction for ToBinary {
132    fn signature(&self) -> FunctionSignature {
133        FunctionSignature {
134            name: "TO_BINARY",
135            category: FunctionCategory::Mathematical,
136            arg_count: ArgCount::Fixed(1),
137            description: "Convert a number to binary string representation",
138            returns: "Binary string",
139            examples: vec![
140                "SELECT TO_BINARY(42)   -- Returns '101010'",
141                "SELECT TO_BINARY(255)  -- Returns '11111111'",
142                "SELECT TO_BINARY(-5)   -- Returns '-101'",
143            ],
144        }
145    }
146
147    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
148        if args.len() != 1 {
149            return Err(anyhow!("TO_BINARY requires exactly 1 argument"));
150        }
151
152        let to_base = ToBase;
153        to_base.evaluate(&[args[0].clone(), DataValue::Integer(2)])
154    }
155}
156
157/// Convert from binary string
158pub struct FromBinary;
159
160impl SqlFunction for FromBinary {
161    fn signature(&self) -> FunctionSignature {
162        FunctionSignature {
163            name: "FROM_BINARY",
164            category: FunctionCategory::Mathematical,
165            arg_count: ArgCount::Fixed(1),
166            description: "Convert a binary string to a number",
167            returns: "Numeric value",
168            examples: vec![
169                "SELECT FROM_BINARY('101010')   -- Returns 42",
170                "SELECT FROM_BINARY('11111111') -- Returns 255",
171                "SELECT FROM_BINARY('-101')     -- Returns -5",
172            ],
173        }
174    }
175
176    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
177        if args.len() != 1 {
178            return Err(anyhow!("FROM_BINARY requires exactly 1 argument"));
179        }
180
181        let from_base = FromBase;
182        from_base.evaluate(&[args[0].clone(), DataValue::Integer(2)])
183    }
184}
185
186/// Convert to hexadecimal string
187pub struct ToHex;
188
189impl SqlFunction for ToHex {
190    fn signature(&self) -> FunctionSignature {
191        FunctionSignature {
192            name: "TO_HEX",
193            category: FunctionCategory::Mathematical,
194            arg_count: ArgCount::Fixed(1),
195            description: "Convert a number to hexadecimal string representation",
196            returns: "Hexadecimal string",
197            examples: vec![
198                "SELECT TO_HEX(255)    -- Returns 'ff'",
199                "SELECT TO_HEX(4095)   -- Returns 'fff'",
200                "SELECT TO_HEX(16777215) -- Returns 'ffffff'",
201            ],
202        }
203    }
204
205    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
206        if args.len() != 1 {
207            return Err(anyhow!("TO_HEX requires exactly 1 argument"));
208        }
209
210        let to_base = ToBase;
211        to_base.evaluate(&[args[0].clone(), DataValue::Integer(16)])
212    }
213}
214
215/// Convert from hexadecimal string
216pub struct FromHex;
217
218impl SqlFunction for FromHex {
219    fn signature(&self) -> FunctionSignature {
220        FunctionSignature {
221            name: "FROM_HEX",
222            category: FunctionCategory::Mathematical,
223            arg_count: ArgCount::Fixed(1),
224            description: "Convert a hexadecimal string to a number",
225            returns: "Numeric value",
226            examples: vec![
227                "SELECT FROM_HEX('ff')     -- Returns 255",
228                "SELECT FROM_HEX('fff')    -- Returns 4095",
229                "SELECT FROM_HEX('ffffff') -- Returns 16777215",
230            ],
231        }
232    }
233
234    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
235        if args.len() != 1 {
236            return Err(anyhow!("FROM_HEX requires exactly 1 argument"));
237        }
238
239        let from_base = FromBase;
240        from_base.evaluate(&[args[0].clone(), DataValue::Integer(16)])
241    }
242}
243
244/// Convert to octal string
245pub struct ToOctal;
246
247impl SqlFunction for ToOctal {
248    fn signature(&self) -> FunctionSignature {
249        FunctionSignature {
250            name: "TO_OCTAL",
251            category: FunctionCategory::Mathematical,
252            arg_count: ArgCount::Fixed(1),
253            description: "Convert a number to octal string representation",
254            returns: "Octal string",
255            examples: vec![
256                "SELECT TO_OCTAL(8)    -- Returns '10'",
257                "SELECT TO_OCTAL(64)   -- Returns '100'",
258                "SELECT TO_OCTAL(511)  -- Returns '777'",
259            ],
260        }
261    }
262
263    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
264        if args.len() != 1 {
265            return Err(anyhow!("TO_OCTAL requires exactly 1 argument"));
266        }
267
268        let to_base = ToBase;
269        to_base.evaluate(&[args[0].clone(), DataValue::Integer(8)])
270    }
271}
272
273/// Convert from octal string
274pub struct FromOctal;
275
276impl SqlFunction for FromOctal {
277    fn signature(&self) -> FunctionSignature {
278        FunctionSignature {
279            name: "FROM_OCTAL",
280            category: FunctionCategory::Mathematical,
281            arg_count: ArgCount::Fixed(1),
282            description: "Convert an octal string to a number",
283            returns: "Numeric value",
284            examples: vec![
285                "SELECT FROM_OCTAL('10')   -- Returns 8",
286                "SELECT FROM_OCTAL('100')  -- Returns 64",
287                "SELECT FROM_OCTAL('777')  -- Returns 511",
288            ],
289        }
290    }
291
292    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
293        if args.len() != 1 {
294            return Err(anyhow!("FROM_OCTAL requires exactly 1 argument"));
295        }
296
297        let from_base = FromBase;
298        from_base.evaluate(&[args[0].clone(), DataValue::Integer(8)])
299    }
300}
301
302// Helper function to convert number to string in any base
303fn to_base_string(mut num: u64, base: u32) -> String {
304    if num == 0 {
305        return "0".to_string();
306    }
307
308    const DIGITS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
309    let mut result = Vec::new();
310
311    while num > 0 {
312        let remainder = (num % base as u64) as usize;
313        result.push(DIGITS[remainder]);
314        num /= base as u64;
315    }
316
317    result.reverse();
318    String::from_utf8(result).unwrap()
319}
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324
325    #[test]
326    fn test_to_base() {
327        let func = ToBase;
328
329        // Binary
330        assert_eq!(
331            func.evaluate(&[DataValue::Integer(42), DataValue::Integer(2)])
332                .unwrap(),
333            DataValue::String("101010".to_string())
334        );
335
336        // Hexadecimal
337        assert_eq!(
338            func.evaluate(&[DataValue::Integer(255), DataValue::Integer(16)])
339                .unwrap(),
340            DataValue::String("ff".to_string())
341        );
342
343        // Octal
344        assert_eq!(
345            func.evaluate(&[DataValue::Integer(64), DataValue::Integer(8)])
346                .unwrap(),
347            DataValue::String("100".to_string())
348        );
349
350        // Base 36
351        assert_eq!(
352            func.evaluate(&[DataValue::Integer(1295), DataValue::Integer(36)])
353                .unwrap(),
354            DataValue::String("zz".to_string())
355        );
356    }
357
358    #[test]
359    fn test_from_base() {
360        let func = FromBase;
361
362        // Binary
363        assert_eq!(
364            func.evaluate(&[
365                DataValue::String("101010".to_string()),
366                DataValue::Integer(2)
367            ])
368            .unwrap(),
369            DataValue::Integer(42)
370        );
371
372        // Hexadecimal
373        assert_eq!(
374            func.evaluate(&[DataValue::String("ff".to_string()), DataValue::Integer(16)])
375                .unwrap(),
376            DataValue::Integer(255)
377        );
378
379        // Octal
380        assert_eq!(
381            func.evaluate(&[DataValue::String("100".to_string()), DataValue::Integer(8)])
382                .unwrap(),
383            DataValue::Integer(64)
384        );
385    }
386
387    #[test]
388    fn test_binary_functions() {
389        let to_bin = ToBinary;
390        let from_bin = FromBinary;
391
392        assert_eq!(
393            to_bin.evaluate(&[DataValue::Integer(42)]).unwrap(),
394            DataValue::String("101010".to_string())
395        );
396
397        assert_eq!(
398            from_bin
399                .evaluate(&[DataValue::String("101010".to_string())])
400                .unwrap(),
401            DataValue::Integer(42)
402        );
403    }
404
405    #[test]
406    fn test_hex_functions() {
407        let to_hex = ToHex;
408        let from_hex = FromHex;
409
410        assert_eq!(
411            to_hex.evaluate(&[DataValue::Integer(255)]).unwrap(),
412            DataValue::String("ff".to_string())
413        );
414
415        assert_eq!(
416            from_hex
417                .evaluate(&[DataValue::String("ff".to_string())])
418                .unwrap(),
419            DataValue::Integer(255)
420        );
421    }
422}