1use anyhow::{anyhow, Result};
2use std::collections::HashMap;
3
4use super::{ArgCount, FunctionCategory, FunctionSignature, SqlFunction};
5use crate::data::datatable::DataValue;
6
7struct SolarBody {
9 mass_kg: f64,
10 radius_m: f64,
11 distance_from_sun_m: f64,
12 orbital_period_days: f64,
13 gravity_ms2: f64, 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 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, moons: 0,
35 });
36
37 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, moons: 0,
48 });
49
50 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, moons: 0,
61 });
62
63 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 m.insert("moon", SolarBody {
78 mass_kg: 7.342e22,
79 radius_m: 1.7374e6,
80 distance_from_sun_m: 1.496e11, orbital_period_days: 27.322, gravity_ms2: 1.62,
83 density_kgm3: 3344.0,
84 escape_velocity_ms: 2380.0,
85 rotation_period_hours: 655.73, moons: 0,
87 });
88
89 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 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, });
114
115 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, });
127
128 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, moons: 27,
139 });
140
141 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 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, moons: 5,
165 });
166
167 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 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
197pub 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
232pub 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
266pub 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
300pub 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
338pub 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
373pub 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
407pub 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
445pub 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
483pub 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 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 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 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 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}