sql_cli/sql/functions/
solar_system.rs

1use anyhow::{anyhow, Result};
2use std::collections::HashMap;
3
4use super::{ArgCount, FunctionCategory, FunctionSignature, SqlFunction};
5use crate::data::datatable::DataValue;
6
7// Solar system data structure
8struct SolarBody {
9    mass_kg: f64,
10    radius_m: f64,
11    distance_from_sun_m: f64,
12    orbital_period_days: f64,
13    gravity_ms2: f64, // Surface gravity
14    density_kgm3: f64,
15    escape_velocity_ms: f64,
16    rotation_period_hours: f64,
17    moons: i32,
18}
19
20lazy_static::lazy_static! {
21    static ref SOLAR_BODIES: HashMap<&'static str, SolarBody> = {
22        let mut m = HashMap::new();
23
24        // Sun
25        m.insert("sun", SolarBody {
26            mass_kg: 1.989e30,
27            radius_m: 6.96e8,
28            distance_from_sun_m: 0.0,
29            orbital_period_days: 0.0,
30            gravity_ms2: 274.0,
31            density_kgm3: 1408.0,
32            escape_velocity_ms: 617500.0,
33            rotation_period_hours: 609.12, // 25.38 days at equator
34            moons: 0,
35        });
36
37        // Mercury
38        m.insert("mercury", SolarBody {
39            mass_kg: 3.3011e23,
40            radius_m: 2.4397e6,
41            distance_from_sun_m: 5.791e10,
42            orbital_period_days: 87.969,
43            gravity_ms2: 3.7,
44            density_kgm3: 5427.0,
45            escape_velocity_ms: 4250.0,
46            rotation_period_hours: 1407.6, // 58.65 days
47            moons: 0,
48        });
49
50        // Venus
51        m.insert("venus", SolarBody {
52            mass_kg: 4.8675e24,
53            radius_m: 6.0518e6,
54            distance_from_sun_m: 1.082e11,
55            orbital_period_days: 224.701,
56            gravity_ms2: 8.87,
57            density_kgm3: 5243.0,
58            escape_velocity_ms: 10360.0,
59            rotation_period_hours: 5832.5, // 243.02 days (retrograde)
60            moons: 0,
61        });
62
63        // Earth
64        m.insert("earth", SolarBody {
65            mass_kg: 5.97237e24,
66            radius_m: 6.371e6,
67            distance_from_sun_m: 1.496e11,
68            orbital_period_days: 365.256,
69            gravity_ms2: 9.807,
70            density_kgm3: 5514.0,
71            escape_velocity_ms: 11186.0,
72            rotation_period_hours: 23.9345,
73            moons: 1,
74        });
75
76        // Moon
77        m.insert("moon", SolarBody {
78            mass_kg: 7.342e22,
79            radius_m: 1.7374e6,
80            distance_from_sun_m: 1.496e11, // Same as Earth
81            orbital_period_days: 27.322, // Around Earth
82            gravity_ms2: 1.62,
83            density_kgm3: 3344.0,
84            escape_velocity_ms: 2380.0,
85            rotation_period_hours: 655.73, // 27.32 days (tidally locked)
86            moons: 0,
87        });
88
89        // Mars
90        m.insert("mars", SolarBody {
91            mass_kg: 6.4171e23,
92            radius_m: 3.3895e6,
93            distance_from_sun_m: 2.279e11,
94            orbital_period_days: 686.971,
95            gravity_ms2: 3.71,
96            density_kgm3: 3933.0,
97            escape_velocity_ms: 5030.0,
98            rotation_period_hours: 24.6229,
99            moons: 2,
100        });
101
102        // Jupiter
103        m.insert("jupiter", SolarBody {
104            mass_kg: 1.8982e27,
105            radius_m: 6.9911e7,
106            distance_from_sun_m: 7.786e11,
107            orbital_period_days: 4332.59,
108            gravity_ms2: 24.79,
109            density_kgm3: 1326.0,
110            escape_velocity_ms: 59500.0,
111            rotation_period_hours: 9.9250,
112            moons: 95, // As of 2023
113        });
114
115        // Saturn
116        m.insert("saturn", SolarBody {
117            mass_kg: 5.6834e26,
118            radius_m: 5.8232e7,
119            distance_from_sun_m: 1.4335e12,
120            orbital_period_days: 10759.22,
121            gravity_ms2: 10.44,
122            density_kgm3: 687.0,
123            escape_velocity_ms: 35500.0,
124            rotation_period_hours: 10.656,
125            moons: 146, // As of 2023
126        });
127
128        // Uranus
129        m.insert("uranus", SolarBody {
130            mass_kg: 8.6810e25,
131            radius_m: 2.5362e7,
132            distance_from_sun_m: 2.8725e12,
133            orbital_period_days: 30688.5,
134            gravity_ms2: 8.87,
135            density_kgm3: 1271.0,
136            escape_velocity_ms: 21300.0,
137            rotation_period_hours: 17.24, // Retrograde
138            moons: 27,
139        });
140
141        // Neptune
142        m.insert("neptune", SolarBody {
143            mass_kg: 1.02413e26,
144            radius_m: 2.4622e7,
145            distance_from_sun_m: 4.4951e12,
146            orbital_period_days: 60195.0,
147            gravity_ms2: 11.15,
148            density_kgm3: 1638.0,
149            escape_velocity_ms: 23500.0,
150            rotation_period_hours: 16.11,
151            moons: 16,
152        });
153
154        // Pluto (dwarf planet)
155        m.insert("pluto", SolarBody {
156            mass_kg: 1.303e22,
157            radius_m: 1.1883e6,
158            distance_from_sun_m: 5.9064e12,
159            orbital_period_days: 90560.0,
160            gravity_ms2: 0.62,
161            density_kgm3: 1854.0,
162            escape_velocity_ms: 1210.0,
163            rotation_period_hours: 153.29, // 6.387 days
164            moons: 5,
165        });
166
167        // Ceres (dwarf planet)
168        m.insert("ceres", SolarBody {
169            mass_kg: 9.393e20,
170            radius_m: 4.73e5,
171            distance_from_sun_m: 4.14e11,
172            orbital_period_days: 1682.0,
173            gravity_ms2: 0.27,
174            density_kgm3: 2162.0,
175            escape_velocity_ms: 510.0,
176            rotation_period_hours: 9.07,
177            moons: 0,
178        });
179
180        // Eris (dwarf planet)
181        m.insert("eris", SolarBody {
182            mass_kg: 1.66e22,
183            radius_m: 1.163e6,
184            distance_from_sun_m: 1.0123e13,
185            orbital_period_days: 203830.0,
186            gravity_ms2: 0.82,
187            density_kgm3: 2520.0,
188            escape_velocity_ms: 1380.0,
189            rotation_period_hours: 25.92,
190            moons: 1,
191        });
192
193        m
194    };
195}
196
197// Mass lookup function
198pub struct MassSolarBodyFunction;
199
200impl SqlFunction for MassSolarBodyFunction {
201    fn signature(&self) -> FunctionSignature {
202        FunctionSignature {
203            name: "MASS_SOLAR_BODY",
204            category: FunctionCategory::Astronomical,
205            arg_count: ArgCount::Fixed(1),
206            description: "Returns the mass of a solar system body in kg",
207            returns: "FLOAT",
208            examples: vec![
209                "SELECT MASS_SOLAR_BODY('earth')",
210                "SELECT MASS_SOLAR_BODY('pluto')",
211                "SELECT body_name, MASS_SOLAR_BODY(body_name) FROM solar_system",
212            ],
213        }
214    }
215
216    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
217        self.validate_args(args)?;
218
219        let body_name = match &args[0] {
220            DataValue::String(s) => s.to_lowercase(),
221            DataValue::Null => return Ok(DataValue::Null),
222            _ => return Err(anyhow!("MASS_SOLAR_BODY requires a string argument")),
223        };
224
225        match SOLAR_BODIES.get(body_name.as_str()) {
226            Some(body) => Ok(DataValue::Float(body.mass_kg)),
227            None => Err(anyhow!("Unknown solar body: {}", body_name)),
228        }
229    }
230}
231
232// Radius lookup function
233pub struct RadiusSolarBodyFunction;
234
235impl SqlFunction for RadiusSolarBodyFunction {
236    fn signature(&self) -> FunctionSignature {
237        FunctionSignature {
238            name: "RADIUS_SOLAR_BODY",
239            category: FunctionCategory::Astronomical,
240            arg_count: ArgCount::Fixed(1),
241            description: "Returns the radius of a solar system body in meters",
242            returns: "FLOAT",
243            examples: vec![
244                "SELECT RADIUS_SOLAR_BODY('earth')",
245                "SELECT RADIUS_SOLAR_BODY('jupiter')",
246            ],
247        }
248    }
249
250    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
251        self.validate_args(args)?;
252
253        let body_name = match &args[0] {
254            DataValue::String(s) => s.to_lowercase(),
255            DataValue::Null => return Ok(DataValue::Null),
256            _ => return Err(anyhow!("RADIUS_SOLAR_BODY requires a string argument")),
257        };
258
259        match SOLAR_BODIES.get(body_name.as_str()) {
260            Some(body) => Ok(DataValue::Float(body.radius_m)),
261            None => Err(anyhow!("Unknown solar body: {}", body_name)),
262        }
263    }
264}
265
266// Distance from Sun lookup function
267pub struct DistanceSolarBodyFunction;
268
269impl SqlFunction for DistanceSolarBodyFunction {
270    fn signature(&self) -> FunctionSignature {
271        FunctionSignature {
272            name: "DISTANCE_SOLAR_BODY",
273            category: FunctionCategory::Astronomical,
274            arg_count: ArgCount::Fixed(1),
275            description: "Returns the mean distance from the Sun in meters",
276            returns: "FLOAT",
277            examples: vec![
278                "SELECT DISTANCE_SOLAR_BODY('mars')",
279                "SELECT DISTANCE_SOLAR_BODY('neptune') / AU() AS neptune_au",
280            ],
281        }
282    }
283
284    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
285        self.validate_args(args)?;
286
287        let body_name = match &args[0] {
288            DataValue::String(s) => s.to_lowercase(),
289            DataValue::Null => return Ok(DataValue::Null),
290            _ => return Err(anyhow!("DISTANCE_SOLAR_BODY requires a string argument")),
291        };
292
293        match SOLAR_BODIES.get(body_name.as_str()) {
294            Some(body) => Ok(DataValue::Float(body.distance_from_sun_m)),
295            None => Err(anyhow!("Unknown solar body: {}", body_name)),
296        }
297    }
298}
299
300// Orbital period lookup function
301pub struct OrbitalPeriodSolarBodyFunction;
302
303impl SqlFunction for OrbitalPeriodSolarBodyFunction {
304    fn signature(&self) -> FunctionSignature {
305        FunctionSignature {
306            name: "ORBITAL_PERIOD_SOLAR_BODY",
307            category: FunctionCategory::Astronomical,
308            arg_count: ArgCount::Fixed(1),
309            description: "Returns the orbital period in days",
310            returns: "FLOAT",
311            examples: vec![
312                "SELECT ORBITAL_PERIOD_SOLAR_BODY('earth')",
313                "SELECT ORBITAL_PERIOD_SOLAR_BODY('pluto') / 365.256 AS pluto_years",
314            ],
315        }
316    }
317
318    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
319        self.validate_args(args)?;
320
321        let body_name = match &args[0] {
322            DataValue::String(s) => s.to_lowercase(),
323            DataValue::Null => return Ok(DataValue::Null),
324            _ => {
325                return Err(anyhow!(
326                    "ORBITAL_PERIOD_SOLAR_BODY requires a string argument"
327                ))
328            }
329        };
330
331        match SOLAR_BODIES.get(body_name.as_str()) {
332            Some(body) => Ok(DataValue::Float(body.orbital_period_days)),
333            None => Err(anyhow!("Unknown solar body: {}", body_name)),
334        }
335    }
336}
337
338// Surface gravity lookup function
339pub struct GravitySolarBodyFunction;
340
341impl SqlFunction for GravitySolarBodyFunction {
342    fn signature(&self) -> FunctionSignature {
343        FunctionSignature {
344            name: "GRAVITY_SOLAR_BODY",
345            category: FunctionCategory::Astronomical,
346            arg_count: ArgCount::Fixed(1),
347            description: "Returns the surface gravity in m/s²",
348            returns: "FLOAT",
349            examples: vec![
350                "SELECT GRAVITY_SOLAR_BODY('earth')",
351                "SELECT GRAVITY_SOLAR_BODY('moon')",
352                "SELECT body, GRAVITY_SOLAR_BODY(body) / 9.807 AS earth_g FROM planets",
353            ],
354        }
355    }
356
357    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
358        self.validate_args(args)?;
359
360        let body_name = match &args[0] {
361            DataValue::String(s) => s.to_lowercase(),
362            DataValue::Null => return Ok(DataValue::Null),
363            _ => return Err(anyhow!("GRAVITY_SOLAR_BODY requires a string argument")),
364        };
365
366        match SOLAR_BODIES.get(body_name.as_str()) {
367            Some(body) => Ok(DataValue::Float(body.gravity_ms2)),
368            None => Err(anyhow!("Unknown solar body: {}", body_name)),
369        }
370    }
371}
372
373// Density lookup function
374pub struct DensitySolarBodyFunction;
375
376impl SqlFunction for DensitySolarBodyFunction {
377    fn signature(&self) -> FunctionSignature {
378        FunctionSignature {
379            name: "DENSITY_SOLAR_BODY",
380            category: FunctionCategory::Astronomical,
381            arg_count: ArgCount::Fixed(1),
382            description: "Returns the density in kg/m³",
383            returns: "FLOAT",
384            examples: vec![
385                "SELECT DENSITY_SOLAR_BODY('saturn')",
386                "SELECT body, DENSITY_SOLAR_BODY(body) FROM planets ORDER BY DENSITY_SOLAR_BODY(body)",
387            ],
388        }
389    }
390
391    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
392        self.validate_args(args)?;
393
394        let body_name = match &args[0] {
395            DataValue::String(s) => s.to_lowercase(),
396            DataValue::Null => return Ok(DataValue::Null),
397            _ => return Err(anyhow!("DENSITY_SOLAR_BODY requires a string argument")),
398        };
399
400        match SOLAR_BODIES.get(body_name.as_str()) {
401            Some(body) => Ok(DataValue::Float(body.density_kgm3)),
402            None => Err(anyhow!("Unknown solar body: {}", body_name)),
403        }
404    }
405}
406
407// Escape velocity lookup function
408pub struct EscapeVelocitySolarBodyFunction;
409
410impl SqlFunction for EscapeVelocitySolarBodyFunction {
411    fn signature(&self) -> FunctionSignature {
412        FunctionSignature {
413            name: "ESCAPE_VELOCITY_SOLAR_BODY",
414            category: FunctionCategory::Astronomical,
415            arg_count: ArgCount::Fixed(1),
416            description: "Returns the escape velocity in m/s",
417            returns: "FLOAT",
418            examples: vec![
419                "SELECT ESCAPE_VELOCITY_SOLAR_BODY('earth')",
420                "SELECT body, ESCAPE_VELOCITY_SOLAR_BODY(body) / 1000 AS km_per_s FROM planets",
421            ],
422        }
423    }
424
425    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
426        self.validate_args(args)?;
427
428        let body_name = match &args[0] {
429            DataValue::String(s) => s.to_lowercase(),
430            DataValue::Null => return Ok(DataValue::Null),
431            _ => {
432                return Err(anyhow!(
433                    "ESCAPE_VELOCITY_SOLAR_BODY requires a string argument"
434                ))
435            }
436        };
437
438        match SOLAR_BODIES.get(body_name.as_str()) {
439            Some(body) => Ok(DataValue::Float(body.escape_velocity_ms)),
440            None => Err(anyhow!("Unknown solar body: {}", body_name)),
441        }
442    }
443}
444
445// Rotation period lookup function
446pub struct RotationPeriodSolarBodyFunction;
447
448impl SqlFunction for RotationPeriodSolarBodyFunction {
449    fn signature(&self) -> FunctionSignature {
450        FunctionSignature {
451            name: "ROTATION_PERIOD_SOLAR_BODY",
452            category: FunctionCategory::Astronomical,
453            arg_count: ArgCount::Fixed(1),
454            description: "Returns the rotation period in hours",
455            returns: "FLOAT",
456            examples: vec![
457                "SELECT ROTATION_PERIOD_SOLAR_BODY('earth')",
458                "SELECT body, ROTATION_PERIOD_SOLAR_BODY(body) / 24 AS days FROM planets",
459            ],
460        }
461    }
462
463    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
464        self.validate_args(args)?;
465
466        let body_name = match &args[0] {
467            DataValue::String(s) => s.to_lowercase(),
468            DataValue::Null => return Ok(DataValue::Null),
469            _ => {
470                return Err(anyhow!(
471                    "ROTATION_PERIOD_SOLAR_BODY requires a string argument"
472                ))
473            }
474        };
475
476        match SOLAR_BODIES.get(body_name.as_str()) {
477            Some(body) => Ok(DataValue::Float(body.rotation_period_hours)),
478            None => Err(anyhow!("Unknown solar body: {}", body_name)),
479        }
480    }
481}
482
483// Number of moons lookup function
484pub struct MoonsSolarBodyFunction;
485
486impl SqlFunction for MoonsSolarBodyFunction {
487    fn signature(&self) -> FunctionSignature {
488        FunctionSignature {
489            name: "MOONS_SOLAR_BODY",
490            category: FunctionCategory::Astronomical,
491            arg_count: ArgCount::Fixed(1),
492            description: "Returns the number of known moons",
493            returns: "INTEGER",
494            examples: vec![
495                "SELECT MOONS_SOLAR_BODY('jupiter')",
496                "SELECT body, MOONS_SOLAR_BODY(body) FROM planets ORDER BY MOONS_SOLAR_BODY(body) DESC",
497            ],
498        }
499    }
500
501    fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
502        self.validate_args(args)?;
503
504        let body_name = match &args[0] {
505            DataValue::String(s) => s.to_lowercase(),
506            DataValue::Null => return Ok(DataValue::Null),
507            _ => return Err(anyhow!("MOONS_SOLAR_BODY requires a string argument")),
508        };
509
510        match SOLAR_BODIES.get(body_name.as_str()) {
511            Some(body) => Ok(DataValue::Integer(body.moons as i64)),
512            None => Err(anyhow!("Unknown solar body: {}", body_name)),
513        }
514    }
515}
516
517#[cfg(test)]
518mod tests {
519    use super::*;
520
521    #[test]
522    fn test_mass_solar_body() {
523        let func = MassSolarBodyFunction;
524
525        // Test Earth
526        let result = func
527            .evaluate(&[DataValue::String("earth".to_string())])
528            .unwrap();
529        match result {
530            DataValue::Float(val) => assert_eq!(val, 5.97237e24),
531            _ => panic!("Expected Float"),
532        }
533
534        // Test Pluto
535        let result = func
536            .evaluate(&[DataValue::String("pluto".to_string())])
537            .unwrap();
538        match result {
539            DataValue::Float(val) => assert_eq!(val, 1.303e22),
540            _ => panic!("Expected Float"),
541        }
542    }
543
544    #[test]
545    fn test_gravity_solar_body() {
546        let func = GravitySolarBodyFunction;
547
548        // Test Earth
549        let result = func
550            .evaluate(&[DataValue::String("earth".to_string())])
551            .unwrap();
552        match result {
553            DataValue::Float(val) => assert_eq!(val, 9.807),
554            _ => panic!("Expected Float"),
555        }
556
557        // Test Moon
558        let result = func
559            .evaluate(&[DataValue::String("moon".to_string())])
560            .unwrap();
561        match result {
562            DataValue::Float(val) => assert_eq!(val, 1.62),
563            _ => panic!("Expected Float"),
564        }
565    }
566}