1use super::backend::{self, BackendRow, Connection, Params};
4use super::{Database, Row, SelectQuery, TableSchemaProvider};
5use crate::io::api::{self, DatabasePersistence};
6use crate::io::database::macros::{build_query, define_required_fn};
7use crate::io::{create_progress_bar, finish_progress_bar, ApiResult, ProgressType};
8use crate::prelude::PathBuf;
9use crate::util::{print_values_as_table, Label};
10use acorn_macros::DatabaseRow;
11use async_trait::async_trait;
12use bon::Builder;
13use chrono::{DateTime, Utc};
14use color_eyre::eyre::eyre;
15use core::fmt;
16use serde::{Deserialize, Serialize};
17
18define_required_fn!(1, required1, A, a);
19define_required_fn!(3, required3, A, a, B, b, C, c);
20define_required_fn!(4, required4, A, a, B, b, C, c, D, d);
21define_required_fn!(5, required5, A, a, B, b, C, c, D, d, E, e);
22#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
24#[repr(i32)]
25pub enum Table {
26 Activity = 0,
28 Catalog = 1,
30 Licenses = 2,
32 LinkCache = 3,
34 ValidationHistory = 4,
36 ResearchActivityCache = 5,
38 ProgrammingLanguages = 6,
40 Models = 7,
42 Providers = 8,
44}
45#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
47#[builder(start_fn = init, on(String, into))]
48#[row(table = activity, order_by = "executed_at DESC")]
49pub struct ActivityRow {
50 pub id: Option<i64>,
52 pub command: Option<String>,
54 pub executed_at: Option<DateTime<Utc>>,
56 pub user_path: Option<String>,
58 pub success: Option<bool>,
60}
61#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
63#[builder(start_fn = init, on(String, into))]
64#[row(table = catalog, order_by = "title")]
65pub struct CatalogRow {
66 pub id: Option<i64>,
68 pub bucket_name: Option<String>,
70 pub bucket_url: Option<String>,
72 pub identifier: Option<String>,
74 pub title: Option<String>,
76 pub updated_at: Option<DateTime<Utc>>,
78}
79#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
81#[builder(start_fn = init, on(String, into))]
82#[row(table = licenses)]
83pub struct LicenseRow {
84 pub id: Option<i64>,
86 pub identifier: Option<String>,
88 pub name: Option<String>,
90 pub is_deprecated: Option<bool>,
92 pub is_free_software: Option<bool>,
94 pub is_open_source: Option<bool>,
96}
97#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
99#[builder(start_fn = init, on(String, into))]
100#[row(table = programming_languages, order_by = "name")]
101pub struct ProgrammingLanguageRow {
102 pub id: Option<i64>,
104 pub language_id: Option<i64>,
106 pub name: Option<String>,
108 pub language_type: Option<String>,
110 pub color: Option<String>,
112 pub group_name: Option<String>,
114}
115#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
117#[builder(start_fn = init, on(String, into))]
118#[row(table = models)]
119pub struct ModelRow {
120 pub id: Option<i64>,
122 pub model_id: Option<String>,
124 pub name: Option<String>,
126 pub family: Option<String>,
128 pub variant: Option<String>,
130 pub version: Option<String>,
132 pub attachment: Option<bool>,
134 pub open_weights: Option<bool>,
136 pub reasoning: Option<bool>,
138 pub structured_output: Option<bool>,
140 pub temperature: Option<bool>,
142 pub tool_call: Option<bool>,
144 pub parameters: Option<i64>,
146 pub release_date: Option<String>,
148 pub knowledge: Option<String>,
150 pub last_updated: Option<String>,
152 pub limit_context: Option<i64>,
154 pub limit_output: Option<i64>,
156 pub limit_input: Option<i64>,
158 pub modality_input: Option<String>,
160 pub modality_output: Option<String>,
162 pub cost_input: Option<f64>,
164 pub cost_output: Option<f64>,
166 pub cost_cache_read: Option<f64>,
168 pub cost_cache_write: Option<f64>,
170 pub cost_reasoning: Option<f64>,
172 pub cost_input_audio: Option<f64>,
174 pub cost_output_audio: Option<f64>,
176 pub cost_over_200k: Option<String>,
178 pub cost_tiers: Option<String>,
180 pub benchmarks: Option<String>,
182 pub weights: Option<String>,
184}
185#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
187#[builder(start_fn = init, on(String, into))]
188#[row(table = providers)]
189pub struct ProviderRow {
190 pub id: Option<i64>,
192 pub provider_id: Option<String>,
194 pub name: Option<String>,
196 pub description: Option<String>,
198 pub endpoint: Option<String>,
200 pub documentation: Option<String>,
202 pub authentication: Option<String>,
204 pub env: Option<String>,
206 pub npm: Option<String>,
208 pub url: Option<String>,
210 pub established_date: Option<String>,
212 pub last_updated: Option<String>,
214}
215#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
217#[builder(start_fn = init, on(String, into))]
218#[row(table = link_cache)]
219pub struct LinkCacheRow {
220 pub id: Option<i64>,
222 pub url: Option<String>,
224 pub status_code: Option<i32>,
226 pub is_reachable: Option<bool>,
228 pub checked_at: Option<DateTime<Utc>>,
230 pub expires_at: Option<DateTime<Utc>>,
232}
233#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
235#[builder(start_fn = init, on(String, into))]
236#[row(table = research_activity_cache)]
237pub struct ResearchActivityCacheRow {
238 pub id: Option<i64>,
240 pub identifier: Option<String>,
242 pub source_bucket: Option<String>,
244 pub title: Option<String>,
246 pub downloaded_at: Option<DateTime<Utc>>,
248 pub file_path: Option<String>,
250}
251#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
253#[builder(start_fn = init, on(String, into))]
254#[row(table = validation_history, order_by = "checked_at DESC")]
255pub struct ValidationRow {
256 pub id: Option<i64>,
258 pub path: Option<String>,
260 pub check_type: Option<String>,
262 pub success: Option<bool>,
264 pub message: Option<String>,
266 pub checked_at: Option<DateTime<Utc>>,
268}
269impl From<&str> for Table {
270 fn from(value: &str) -> Self {
271 let normalized = value.trim().to_ascii_lowercase();
272 match normalized.as_str() {
273 | "activity" => Self::Activity,
274 | "catalog" => Self::Catalog,
275 | "licenses" | "license" => Self::Licenses,
276 | "link_cache" | "linkcache" => Self::LinkCache,
277 | "validation_history" | "validationhistory" => Self::ValidationHistory,
278 | "research_activity_cache" | "researchactivitycache" => Self::ResearchActivityCache,
279 | "programming_languages" | "programminglanguages" | "languages" | "language" => Self::ProgrammingLanguages,
280 | "models" | "model" => Self::Models,
281 | "providers" | "provider" => Self::Providers,
282 | _ => Self::Activity,
283 }
284 }
285}
286impl From<&BackendRow<'_>> for ModelRow {
287 fn from(row: &BackendRow<'_>) -> Self {
288 ModelRow {
289 id: row.get(0).ok(),
290 model_id: row.get(1).ok(),
291 name: row.get(2).ok(),
292 family: row.get(3).ok(),
293 variant: row.get(4).ok(),
294 version: row.get(5).ok(),
295 attachment: row.get::<_, i32>(6).ok().map(|value| value != 0),
296 open_weights: row.get::<_, i32>(7).ok().map(|value| value != 0),
297 reasoning: row.get::<_, i32>(8).ok().map(|value| value != 0),
298 structured_output: row.get::<_, i32>(9).ok().map(|value| value != 0),
299 temperature: row.get::<_, i32>(10).ok().map(|value| value != 0),
300 tool_call: row.get::<_, i32>(11).ok().map(|value| value != 0),
301 parameters: row.get(12).ok(),
302 release_date: row.get(13).ok(),
303 knowledge: row.get(14).ok(),
304 last_updated: row.get(15).ok(),
305 limit_context: row.get(16).ok(),
306 limit_output: row.get(17).ok(),
307 limit_input: row.get(18).ok(),
308 modality_input: row.get(19).ok(),
309 modality_output: row.get(20).ok(),
310 cost_input: row.get(21).ok(),
311 cost_output: row.get(22).ok(),
312 cost_cache_read: row.get(23).ok(),
313 cost_cache_write: row.get(24).ok(),
314 cost_reasoning: row.get(25).ok(),
315 cost_input_audio: row.get(26).ok(),
316 cost_output_audio: row.get(27).ok(),
317 cost_over_200k: row.get(28).ok(),
318 cost_tiers: row.get(29).ok(),
319 benchmarks: row.get(30).ok(),
320 weights: row.get(31).ok(),
321 }
322 }
323}
324impl From<BackendRow<'_>> for ModelRow {
325 fn from(row: BackendRow<'_>) -> Self {
326 ModelRow::from(&row)
327 }
328}
329impl From<&BackendRow<'_>> for ProviderRow {
330 fn from(row: &BackendRow<'_>) -> Self {
331 ProviderRow {
332 id: row.get(0).ok(),
333 provider_id: row.get(1).ok(),
334 name: row.get(2).ok(),
335 description: row.get(3).ok(),
336 endpoint: row.get(4).ok(),
337 documentation: row.get(5).ok(),
338 authentication: row.get(6).ok(),
339 env: row.get(7).ok(),
340 npm: row.get(8).ok(),
341 url: row.get(9).ok(),
342 established_date: row.get(10).ok(),
343 last_updated: row.get(11).ok(),
344 }
345 }
346}
347impl From<BackendRow<'_>> for ProviderRow {
348 fn from(row: BackendRow<'_>) -> Self {
349 ProviderRow::from(&row)
350 }
351}
352impl From<&BackendRow<'_>> for ActivityRow {
353 fn from(row: &BackendRow<'_>) -> Self {
354 ActivityRow {
355 id: row.get(0).ok(),
356 command: row.get(1).ok(),
357 executed_at: parse_rfc3339_column(row, 2),
358 user_path: row.get(3).ok(),
359 success: row.get::<_, i32>(4).ok().map(|value| value != 0),
360 }
361 }
362}
363impl From<BackendRow<'_>> for ActivityRow {
364 fn from(row: BackendRow<'_>) -> Self {
365 ActivityRow::from(&row)
366 }
367}
368impl From<&BackendRow<'_>> for LicenseRow {
369 fn from(row: &BackendRow<'_>) -> Self {
370 LicenseRow {
371 id: row.get(0).ok(),
372 identifier: row.get(1).ok(),
373 name: row.get(2).ok(),
374 is_deprecated: row.get::<_, i32>(3).ok().map(|value| value != 0),
375 is_free_software: row.get::<_, i32>(4).ok().map(|value| value != 0),
376 is_open_source: row.get::<_, i32>(5).ok().map(|value| value != 0),
377 }
378 }
379}
380impl From<BackendRow<'_>> for LicenseRow {
381 fn from(row: BackendRow<'_>) -> Self {
382 LicenseRow::from(&row)
383 }
384}
385impl From<&BackendRow<'_>> for ProgrammingLanguageRow {
386 fn from(row: &BackendRow<'_>) -> Self {
387 ProgrammingLanguageRow {
388 id: row.get(0).ok(),
389 language_id: row.get(1).ok(),
390 name: row.get(2).ok(),
391 language_type: row.get(3).ok(),
392 color: row.get(4).ok(),
393 group_name: row.get(5).ok(),
394 }
395 }
396}
397impl From<BackendRow<'_>> for ProgrammingLanguageRow {
398 fn from(row: BackendRow<'_>) -> Self {
399 ProgrammingLanguageRow::from(&row)
400 }
401}
402impl From<&BackendRow<'_>> for CatalogRow {
403 fn from(row: &BackendRow<'_>) -> Self {
404 CatalogRow {
405 id: row.get(0).ok(),
406 bucket_name: row.get(1).ok(),
407 bucket_url: row.get(2).ok(),
408 identifier: row.get(3).ok(),
409 title: row.get(4).ok(),
410 updated_at: parse_rfc3339_column(row, 5),
411 }
412 }
413}
414impl From<BackendRow<'_>> for CatalogRow {
415 fn from(row: BackendRow<'_>) -> Self {
416 CatalogRow::from(&row)
417 }
418}
419impl From<&BackendRow<'_>> for LinkCacheRow {
420 fn from(row: &BackendRow<'_>) -> Self {
421 LinkCacheRow {
422 id: row.get(0).ok(),
423 url: row.get(1).ok(),
424 status_code: row.get(2).ok(),
425 is_reachable: row.get::<_, i32>(3).ok().map(|value| value != 0),
426 checked_at: parse_rfc3339_column(row, 4),
427 expires_at: parse_rfc3339_column(row, 5),
428 }
429 }
430}
431impl From<BackendRow<'_>> for LinkCacheRow {
432 fn from(row: BackendRow<'_>) -> Self {
433 LinkCacheRow::from(&row)
434 }
435}
436impl From<&BackendRow<'_>> for ResearchActivityCacheRow {
437 fn from(row: &BackendRow<'_>) -> Self {
438 ResearchActivityCacheRow {
439 id: row.get(0).ok(),
440 identifier: row.get(1).ok(),
441 source_bucket: row.get(2).ok(),
442 title: row.get(3).ok(),
443 downloaded_at: parse_rfc3339_column(row, 4),
444 file_path: row.get(5).ok(),
445 }
446 }
447}
448impl From<BackendRow<'_>> for ResearchActivityCacheRow {
449 fn from(row: BackendRow<'_>) -> Self {
450 ResearchActivityCacheRow::from(&row)
451 }
452}
453impl From<&BackendRow<'_>> for ValidationRow {
454 fn from(row: &BackendRow<'_>) -> Self {
455 ValidationRow {
456 id: row.get(0).ok(),
457 path: row.get(1).ok(),
458 check_type: row.get(2).ok(),
459 success: row.get::<_, i32>(3).ok().map(|value| value != 0),
460 message: row.get(4).ok(),
461 checked_at: parse_rfc3339_column(row, 5),
462 }
463 }
464}
465impl From<BackendRow<'_>> for ValidationRow {
466 fn from(row: BackendRow<'_>) -> Self {
467 ValidationRow::from(&row)
468 }
469}
470impl From<ActivityRow> for Vec<String> {
471 fn from(row: ActivityRow) -> Self {
472 vec![
473 row.id.map_or_else(String::new, |v| v.to_string()),
474 row.command.unwrap_or_default(),
475 row.executed_at.map_or_else(String::new, |v| v.to_rfc3339()),
476 row.user_path.unwrap_or_default(),
477 row.success.map_or_else(String::new, |v| v.to_string()),
478 ]
479 }
480}
481impl From<CatalogRow> for Vec<String> {
482 fn from(row: CatalogRow) -> Self {
483 vec![
484 row.id.map_or_else(String::new, |v| v.to_string()),
485 row.bucket_name.unwrap_or_default(),
486 row.bucket_url.unwrap_or_default(),
487 row.identifier.unwrap_or_default(),
488 row.title.unwrap_or_default(),
489 row.updated_at.map_or_else(String::new, |v| v.to_rfc3339()),
490 ]
491 }
492}
493impl From<LicenseRow> for Vec<String> {
494 fn from(row: LicenseRow) -> Self {
495 vec![
496 row.id.map_or_else(String::new, |v| v.to_string()),
497 row.identifier.unwrap_or_default(),
498 row.name.unwrap_or_default(),
499 row.is_deprecated.map_or_else(String::new, |v| v.to_string()),
500 row.is_free_software.map_or_else(String::new, |v| v.to_string()),
501 row.is_open_source.map_or_else(String::new, |v| v.to_string()),
502 ]
503 }
504}
505impl From<ProgrammingLanguageRow> for Vec<String> {
506 fn from(row: ProgrammingLanguageRow) -> Self {
507 vec![
508 row.id.map_or_else(String::new, |v| v.to_string()),
509 row.language_id.map_or_else(String::new, |v| v.to_string()),
510 row.name.unwrap_or_default(),
511 row.language_type.unwrap_or_default(),
512 row.color.unwrap_or_default(),
513 row.group_name.unwrap_or_default(),
514 ]
515 }
516}
517impl From<LinkCacheRow> for Vec<String> {
518 fn from(row: LinkCacheRow) -> Self {
519 vec![
520 row.id.map_or_else(String::new, |v| v.to_string()),
521 row.url.unwrap_or_default(),
522 row.status_code.map_or_else(String::new, |v| v.to_string()),
523 row.is_reachable.map_or_else(String::new, |v| v.to_string()),
524 row.checked_at.map_or_else(String::new, |v| v.to_rfc3339()),
525 row.expires_at.map_or_else(String::new, |v| v.to_rfc3339()),
526 ]
527 }
528}
529impl From<ResearchActivityCacheRow> for Vec<String> {
530 fn from(row: ResearchActivityCacheRow) -> Self {
531 vec![
532 row.id.map_or_else(String::new, |v| v.to_string()),
533 row.identifier.unwrap_or_default(),
534 row.source_bucket.unwrap_or_default(),
535 row.title.unwrap_or_default(),
536 row.downloaded_at.map_or_else(String::new, |v| v.to_rfc3339()),
537 row.file_path.unwrap_or_default(),
538 ]
539 }
540}
541impl From<ValidationRow> for Vec<String> {
542 fn from(row: ValidationRow) -> Self {
543 vec![
544 row.id.map_or_else(String::new, |v| v.to_string()),
545 row.path.unwrap_or_default(),
546 row.check_type.unwrap_or_default(),
547 row.success.map_or_else(String::new, |v| v.to_string()),
548 row.message.unwrap_or_default(),
549 row.checked_at.map_or_else(String::new, |v| v.to_rfc3339()),
550 ]
551 }
552}
553impl From<ModelRow> for Vec<String> {
554 fn from(row: ModelRow) -> Self {
555 vec![
556 row.id.map_or_else(String::new, |v| v.to_string()),
557 row.model_id.unwrap_or_default(),
558 row.name.unwrap_or_default(),
559 row.family.unwrap_or_default(),
560 row.variant.unwrap_or_default(),
561 row.version.unwrap_or_default(),
562 row.attachment.map_or_else(String::new, |v| v.to_string()),
563 row.open_weights.map_or_else(String::new, |v| v.to_string()),
564 row.reasoning.map_or_else(String::new, |v| v.to_string()),
565 row.structured_output.map_or_else(String::new, |v| v.to_string()),
566 row.temperature.map_or_else(String::new, |v| v.to_string()),
567 row.tool_call.map_or_else(String::new, |v| v.to_string()),
568 row.parameters.map_or_else(String::new, |v| v.to_string()),
569 row.release_date.unwrap_or_default(),
570 row.knowledge.unwrap_or_default(),
571 row.last_updated.unwrap_or_default(),
572 row.limit_context.map_or_else(String::new, |v| v.to_string()),
573 row.limit_output.map_or_else(String::new, |v| v.to_string()),
574 row.limit_input.map_or_else(String::new, |v| v.to_string()),
575 row.modality_input.unwrap_or_default(),
576 row.modality_output.unwrap_or_default(),
577 row.cost_input.map_or_else(String::new, |v| v.to_string()),
578 row.cost_output.map_or_else(String::new, |v| v.to_string()),
579 row.cost_cache_read.map_or_else(String::new, |v| v.to_string()),
580 row.cost_cache_write.map_or_else(String::new, |v| v.to_string()),
581 row.cost_reasoning.map_or_else(String::new, |v| v.to_string()),
582 row.cost_input_audio.map_or_else(String::new, |v| v.to_string()),
583 row.cost_output_audio.map_or_else(String::new, |v| v.to_string()),
584 row.cost_over_200k.unwrap_or_default(),
585 row.cost_tiers.unwrap_or_default(),
586 row.benchmarks.unwrap_or_default(),
587 row.weights.unwrap_or_default(),
588 ]
589 }
590}
591impl From<ProviderRow> for Vec<String> {
592 fn from(row: ProviderRow) -> Self {
593 vec![
594 row.id.map_or_else(String::new, |v| v.to_string()),
595 row.provider_id.unwrap_or_default(),
596 row.name.unwrap_or_default(),
597 row.description.unwrap_or_default(),
598 row.endpoint.unwrap_or_default(),
599 row.documentation.unwrap_or_default(),
600 row.authentication.unwrap_or_default(),
601 row.env.unwrap_or_default(),
602 row.npm.unwrap_or_default(),
603 row.url.unwrap_or_default(),
604 row.established_date.unwrap_or_default(),
605 row.last_updated.unwrap_or_default(),
606 ]
607 }
608}
609impl fmt::Display for ActivityRow {
610 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
611 write!(
612 f,
613 "ActivityRow = id: {}, command: {}, success: {}",
614 display_or_none(self.id.as_ref()),
615 display_or_none(self.command.as_ref()),
616 self.success.unwrap_or(false),
617 )
618 .and_then(|_| write_optional_field(f, "path", self.user_path.as_ref()))
619 .and_then(|_| write_optional_timestamp(f, "executed_at", self.executed_at.as_ref()))
620 }
621}
622impl fmt::Display for LicenseRow {
623 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
624 write!(
625 f,
626 "LicenseRow = id: {}, identifier: {}, name: {}, deprecated: {}, free: {}, osi: {}",
627 display_or_none(self.id.as_ref()),
628 display_or_none(self.identifier.as_ref()),
629 display_or_none(self.name.as_ref()),
630 self.is_deprecated.unwrap_or(false),
631 self.is_free_software.unwrap_or(false),
632 self.is_open_source.unwrap_or(false),
633 )
634 }
635}
636impl fmt::Display for CatalogRow {
637 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
638 write!(
639 f,
640 "CatalogRow = id: {}, bucket: {}, identifier: {}, title: {}",
641 display_or_none(self.id.as_ref()),
642 display_or_none(self.bucket_name.as_ref()),
643 display_or_none(self.identifier.as_ref()),
644 display_or_none(self.title.as_ref())
645 )
646 .and_then(|_| write_optional_timestamp(f, "updated_at", self.updated_at.as_ref()))
647 }
648}
649impl fmt::Display for ProgrammingLanguageRow {
650 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
651 write!(
652 f,
653 "ProgrammingLanguageRow = id: {}, language_id: {}, name: {}, type: {}",
654 display_or_none(self.id.as_ref()),
655 display_or_none(self.language_id.as_ref()),
656 display_or_none(self.name.as_ref()),
657 display_or_none(self.language_type.as_ref())
658 )
659 .and_then(|_| write_optional_field(f, "color", self.color.as_ref()))
660 .and_then(|_| write_optional_field(f, "group_name", self.group_name.as_ref()))
661 }
662}
663impl fmt::Display for LinkCacheRow {
664 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
665 write!(
666 f,
667 "LinkCacheRow = id: {}, url: {}, status: {}, reachable: {}",
668 display_or_none(self.id.as_ref()),
669 display_or_none(self.url.as_ref()),
670 self.status_code.unwrap_or(0),
671 self.is_reachable.unwrap_or(false)
672 )
673 .and_then(|_| write_optional_timestamp(f, "checked_at", self.checked_at.as_ref()))
674 .and_then(|_| write_optional_timestamp(f, "expires_at", self.expires_at.as_ref()))
675 }
676}
677impl fmt::Display for ResearchActivityCacheRow {
678 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
679 write!(
680 f,
681 "ResearchActivityCacheRow = id: {}, identifier: {}, bucket: {}, title: {}",
682 display_or_none(self.id.as_ref()),
683 display_or_none(self.identifier.as_ref()),
684 display_or_none(self.source_bucket.as_ref()),
685 display_or_none(self.title.as_ref())
686 )
687 .and_then(|_| write_optional_field(f, "file_path", self.file_path.as_ref()))
688 .and_then(|_| write_optional_timestamp(f, "downloaded_at", self.downloaded_at.as_ref()))
689 }
690}
691impl fmt::Display for ValidationRow {
692 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
693 write!(
694 f,
695 "ValidationRow = id: {}, path: {}, type: {}, success: {}, message: {}",
696 display_or_none(self.id.as_ref()),
697 display_or_none(self.path.as_ref()),
698 display_or_none(self.check_type.as_ref()),
699 self.success.unwrap_or(false),
700 display_or_none(self.message.as_ref())
701 )
702 .and_then(|_| write_optional_timestamp(f, "checked_at", self.checked_at.as_ref()))
703 }
704}
705impl fmt::Display for ModelRow {
706 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
707 write!(
708 f,
709 "ModelRow = id: {}, model_id: {}, name: {}, family: {}",
710 display_or_none(self.id.as_ref()),
711 display_or_none(self.model_id.as_ref()),
712 display_or_none(self.name.as_ref()),
713 display_or_none(self.family.as_ref())
714 )
715 }
716}
717impl fmt::Display for ProviderRow {
718 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
719 write!(
720 f,
721 "ProviderRow = id: {}, provider_id: {}, name: {}",
722 display_or_none(self.id.as_ref()),
723 display_or_none(self.provider_id.as_ref()),
724 display_or_none(self.name.as_ref())
725 )
726 .and_then(|_| write_optional_field(f, "description", self.description.as_ref()))
727 }
728}
729#[async_trait]
730impl TableSchemaProvider for Table {
731 fn all() -> &'static [Self] {
732 &[
733 Table::Activity,
734 Table::Catalog,
735 Table::Licenses,
736 Table::LinkCache,
737 Table::ValidationHistory,
738 Table::ResearchActivityCache,
739 Table::ProgrammingLanguages,
740 Table::Models,
741 Table::Providers,
742 ]
743 }
744 fn create_statement(&self) -> String {
746 let columns = match self {
747 | Table::Activity => {
748 r#"
749 command TEXT NOT NULL,
750 executed_at TEXT NOT NULL,
751 user_path TEXT,
752 success INTEGER NOT NULL DEFAULT 1
753 "#
754 }
755 | Table::Catalog => {
756 r#"
757 bucket_name TEXT NOT NULL,
758 bucket_url TEXT NOT NULL,
759 identifier TEXT NOT NULL,
760 title TEXT NOT NULL,
761 updated_at TEXT NOT NULL,
762 UNIQUE(bucket_name, identifier)
763 "#
764 }
765 | Table::Licenses => {
766 r#"
767 identifier TEXT NOT NULL UNIQUE,
768 name TEXT NOT NULL,
769 is_deprecated INTEGER NOT NULL,
770 is_free_software INTEGER NOT NULL,
771 is_open_source INTEGER NOT NULL
772 "#
773 }
774 | Table::LinkCache => {
775 r#"
776 url TEXT NOT NULL UNIQUE,
777 status_code INTEGER NOT NULL,
778 is_reachable INTEGER NOT NULL,
779 checked_at TEXT NOT NULL,
780 expires_at TEXT NOT NULL
781 "#
782 }
783 | Table::ValidationHistory => {
784 r#"
785 path TEXT NOT NULL,
786 check_type TEXT NOT NULL,
787 success INTEGER NOT NULL,
788 message TEXT NOT NULL,
789 checked_at TEXT NOT NULL
790 "#
791 }
792 | Table::ResearchActivityCache => {
793 r#"
794 identifier TEXT NOT NULL UNIQUE,
795 source_bucket TEXT NOT NULL,
796 title TEXT NOT NULL,
797 downloaded_at TEXT NOT NULL,
798 file_path TEXT NOT NULL
799 "#
800 }
801 | Table::ProgrammingLanguages => {
802 r#"
803 language_id INTEGER,
804 name TEXT NOT NULL UNIQUE,
805 language_type TEXT,
806 color TEXT,
807 group_name TEXT
808 "#
809 }
810 | Table::Models => {
811 r#"
812 model_id TEXT NOT NULL UNIQUE,
813 name TEXT,
814 family TEXT,
815 variant TEXT,
816 version TEXT,
817 attachment INTEGER,
818 open_weights INTEGER,
819 reasoning INTEGER,
820 structured_output INTEGER,
821 temperature INTEGER,
822 tool_call INTEGER,
823 parameters INTEGER,
824 release_date TEXT,
825 knowledge TEXT,
826 last_updated TEXT,
827 limit_context INTEGER,
828 limit_output INTEGER,
829 limit_input INTEGER,
830 modality_input TEXT,
831 modality_output TEXT,
832 cost_input REAL,
833 cost_output REAL,
834 cost_cache_read REAL,
835 cost_cache_write REAL,
836 cost_reasoning REAL,
837 cost_input_audio REAL,
838 cost_output_audio REAL,
839 cost_over_200k TEXT,
840 cost_tiers TEXT,
841 benchmarks TEXT,
842 weights TEXT
843 "#
844 }
845 | Table::Providers => {
846 r#"
847 provider_id TEXT NOT NULL UNIQUE,
848 name TEXT,
849 description TEXT,
850 endpoint TEXT,
851 documentation TEXT,
852 authentication TEXT,
853 env TEXT,
854 npm TEXT,
855 url TEXT,
856 established_date TEXT,
857 last_updated TEXT
858 "#
859 }
860 };
861
862 format!(
863 r#"
864 CREATE TABLE IF NOT EXISTS {} (
865 {},
866 {}
867 )
868 "#,
869 self.name(),
870 id_column_definition(),
871 columns.trim()
872 )
873 }
874 fn name(&self) -> &'static str {
876 match self {
877 | Table::Activity => "activity",
878 | Table::Catalog => "catalog",
879 | Table::Licenses => "licenses",
880 | Table::LinkCache => "link_cache",
881 | Table::ValidationHistory => "validation_history",
882 | Table::ResearchActivityCache => "research_activity_cache",
883 | Table::ProgrammingLanguages => "programming_languages",
884 | Table::Models => "models",
885 | Table::Providers => "providers",
886 }
887 }
888 async fn populate(&self) -> ApiResult<usize> {
889 match &self {
890 | Table::Licenses => {
891 let progress = create_progress_bar(0, ProgressType::Spinner);
892 progress.set_message("Downloading SPDX license data...");
893 let response = api::spdx::download().await;
894 let message = format!("{}SPDX metadata download complete", Label::CHECKMARK);
895 finish_progress_bar(&progress, message);
896 match response {
897 | Ok(data) => data.persist(Database::<Self>::from_path(None)).await,
898 | Err(why) => Err(eyre!("Failed to download SPDX license data — {why}")),
899 }
900 }
901 | Table::ProgrammingLanguages => {
902 let progress = create_progress_bar(0, ProgressType::Spinner);
903 progress.set_message("Downloading GitLab programming language data...");
904 let response = api::gitlab::languages().await;
905 let message = format!("{}GitLab programming language metadata download complete", Label::CHECKMARK);
906 finish_progress_bar(&progress, message);
907 match response {
908 | Ok(data) => data.persist(Database::<Self>::from_path(None)).await,
909 | Err(why) => Err(eyre!("Failed to download GitLab programming language data — {why}")),
910 }
911 }
912 | Table::Models => {
913 let progress = create_progress_bar(0, ProgressType::Spinner);
914 progress.set_message("Downloading models.dev catalog (for models)...");
915 let response = api::models_dev::download_cached().await;
916 let message = format!("{}Models.dev catalog download complete", Label::CHECKMARK);
917 finish_progress_bar(&progress, message);
918 match response {
919 | Ok(catalog) => catalog.models().persist(Database::<Self>::from_path(None)).await,
920 | Err(why) => Err(eyre!("Failed to download models.dev catalog — {why}")),
921 }
922 }
923 | Table::Providers => {
924 let progress = create_progress_bar(0, ProgressType::Spinner);
925 progress.set_message("Downloading models.dev catalog (for providers)...");
926 let response = api::models_dev::download_cached().await;
927 let message = format!("{}Models.dev catalog download complete", Label::CHECKMARK);
928 finish_progress_bar(&progress, message);
929 match response {
930 | Ok(catalog) => catalog.providers().persist(Database::<Self>::from_path(None)).await,
931 | Err(why) => Err(eyre!("Failed to download models.dev catalog — {why}")),
932 }
933 }
934 | table => Err(eyre!("populate() not implemented for {} table", table.name())),
935 }
936 }
937 fn print(self, path: Option<PathBuf>) {
945 fn print_rows<R>(table: Table, path: Option<&PathBuf>)
946 where
947 R: Into<Vec<String>> + Row + Default + fmt::Display,
948 for<'row> R: From<&'row BackendRow<'row>>,
949 {
950 let row = R::default();
951 let fields = <R as Row>::fields();
952 let query = format!("SELECT {} FROM {}", fields.join(", "), table.name());
953 let (query, params) = row.build_select_query(&query);
954 let rows = table
955 .rows::<R, _>(&query, params, path)
956 .map(|rows| rows.into_iter().map(Into::into).collect::<Vec<Vec<String>>>())
957 .unwrap_or_default();
958 print_values_as_table::<String>(fields.to_vec(), rows, Some(table.name().to_string()));
959 }
960 match self {
961 | Table::Activity => print_rows::<ActivityRow>(self, path.as_ref()),
962 | Table::Catalog => print_rows::<CatalogRow>(self, path.as_ref()),
963 | Table::Licenses => print_rows::<LicenseRow>(self, path.as_ref()),
964 | Table::ProgrammingLanguages => print_rows::<ProgrammingLanguageRow>(self, path.as_ref()),
965 | Table::LinkCache => print_rows::<LinkCacheRow>(self, path.as_ref()),
966 | Table::ValidationHistory => print_rows::<ValidationRow>(self, path.as_ref()),
967 | Table::ResearchActivityCache => print_rows::<ResearchActivityCacheRow>(self, path.as_ref()),
968 | Table::Models => print_rows::<ModelRow>(self, path.as_ref()),
969 | Table::Providers => print_rows::<ProviderRow>(self, path.as_ref()),
970 }
971 }
972 fn rows<R, P>(&self, query: &str, params: P, path: Option<&PathBuf>) -> ApiResult<Vec<R>>
974 where
975 P: Params,
976 for<'row> R: Row + From<&'row BackendRow<'row>>,
977 {
978 Database::<Table>::from_path(path.cloned()).with_connection(|conn| {
979 conn.prepare(query)
980 .map_err(|why| eyre!("=> {} Failed to prepare {} query: {why}", Label::fail(), self.name()))
981 .and_then(|mut stmt| {
982 stmt.query_map(params, |row| Ok(R::from(row)))
983 .map_err(|why| eyre!("=> {} Failed to query {}: {why}", Label::fail(), self.name()))
984 .and_then(|rows| {
985 rows.collect::<core::result::Result<Vec<_>, _>>()
986 .map_err(|why| eyre!("=> {} Failed to read {} rows: {why}", Label::fail(), self.name()))
987 })
988 })
989 })
990 }
991}
992impl Row for ActivityRow {
993 fn table(&self) -> Table {
994 Table::Activity
995 }
996 fn insert(self, conn: &Connection) -> ApiResult<usize> {
997 let Self {
998 command,
999 executed_at,
1000 user_path,
1001 success,
1002 ..
1003 } = self.clone();
1004 let executed_at = executed_at.unwrap_or_else(Utc::now).to_rfc3339();
1005 let success = i32::from(success.unwrap_or(true));
1006 let required = required1(command);
1007 match required {
1008 | Ok((command,)) => match self.next_row_id(conn) {
1009 | Ok(id) => {
1010 let params = backend::params![id, command, executed_at, user_path, success];
1011 execute_insert(&self, conn, params)
1012 }
1013 | Err(why) => Err(why),
1014 },
1015 | Err(why) => Err(why),
1016 }
1017 }
1018 fn build_select_query(&self, base: &str) -> SelectQuery {
1019 Self::build_select_query(self, base)
1020 }
1021}
1022impl Row for CatalogRow {
1023 fn table(&self) -> Table {
1024 Table::Catalog
1025 }
1026 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1027 let Self {
1028 bucket_name,
1029 bucket_url,
1030 identifier,
1031 title,
1032 updated_at,
1033 ..
1034 } = self.clone();
1035 let updated_at = updated_at.unwrap_or_else(Utc::now).to_rfc3339();
1036 let required = required4(bucket_name, bucket_url, identifier, title);
1037 match required {
1038 | Ok((bucket_name, bucket_url, identifier, title)) => match self.next_row_id(conn) {
1039 | Ok(id) => {
1040 let params = backend::params![id, bucket_name, bucket_url, identifier, title, updated_at];
1041 execute_insert(&self, conn, params)
1042 }
1043 | Err(why) => Err(why),
1044 },
1045 | Err(why) => Err(why),
1046 }
1047 }
1048 fn build_select_query(&self, base: &str) -> SelectQuery {
1049 Self::build_select_query(self, base)
1050 }
1051}
1052impl Row for LicenseRow {
1053 fn table(&self) -> Table {
1054 Table::Licenses
1055 }
1056 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1057 let Self {
1058 identifier,
1059 name,
1060 is_deprecated,
1061 is_free_software,
1062 is_open_source,
1063 ..
1064 } = self.clone();
1065 let required = required5(identifier, name, is_deprecated, is_free_software, is_open_source);
1066 match required {
1067 | Ok((identifier, name, is_deprecated, is_free_software, is_open_source)) => match self.next_row_id(conn) {
1068 | Ok(id) => {
1069 let params = backend::params![
1070 id,
1071 identifier,
1072 name,
1073 i32::from(is_deprecated),
1074 i32::from(is_free_software),
1075 i32::from(is_open_source)
1076 ];
1077 execute_insert(&self, conn, params)
1078 }
1079 | Err(why) => Err(why),
1080 },
1081 | Err(why) => Err(why),
1082 }
1083 }
1084 fn build_select_query(&self, base: &str) -> SelectQuery {
1085 Self::build_select_query(self, base)
1086 }
1087}
1088impl Row for LinkCacheRow {
1089 fn table(&self) -> Table {
1090 Table::LinkCache
1091 }
1092 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1093 let Self {
1094 url,
1095 status_code,
1096 is_reachable,
1097 checked_at,
1098 expires_at,
1099 ..
1100 } = self.clone();
1101 let checked_at = checked_at.unwrap_or_else(Utc::now).to_rfc3339();
1102 let required = required4(url, status_code, is_reachable, expires_at);
1103 match required {
1104 | Ok((url, status_code, is_reachable, expires_at)) => match self.next_row_id(conn) {
1105 | Ok(id) => {
1106 let params = backend::params![id, url, status_code, i32::from(is_reachable), checked_at, expires_at.to_rfc3339()];
1107 execute_insert(&self, conn, params)
1108 }
1109 | Err(why) => Err(why),
1110 },
1111 | Err(why) => Err(why),
1112 }
1113 }
1114 fn build_select_query(&self, base: &str) -> SelectQuery {
1115 Self::build_select_query(self, base)
1116 }
1117}
1118impl Row for ProgrammingLanguageRow {
1119 fn table(&self) -> Table {
1120 Table::ProgrammingLanguages
1121 }
1122 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1123 let Self {
1124 language_id,
1125 name,
1126 language_type,
1127 color,
1128 group_name,
1129 ..
1130 } = self.clone();
1131 let required = required1(name);
1132 match required {
1133 | Ok((name,)) => match self.next_row_id(conn) {
1134 | Ok(id) => {
1135 let params = backend::params![id, language_id, name, language_type, color, group_name];
1136 execute_insert(&self, conn, params)
1137 }
1138 | Err(why) => Err(why),
1139 },
1140 | Err(why) => Err(why),
1141 }
1142 }
1143 fn build_select_query(&self, base: &str) -> SelectQuery {
1144 Self::build_select_query(self, base)
1145 }
1146}
1147impl Row for ResearchActivityCacheRow {
1148 fn table(&self) -> Table {
1149 Table::ResearchActivityCache
1150 }
1151 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1152 let Self {
1153 identifier,
1154 source_bucket,
1155 title,
1156 downloaded_at,
1157 file_path,
1158 ..
1159 } = self.clone();
1160 let downloaded_at = downloaded_at.unwrap_or_else(Utc::now).to_rfc3339();
1161 let required = required4(identifier, source_bucket, title, file_path);
1162 match required {
1163 | Ok((identifier, source_bucket, title, file_path)) => match self.next_row_id(conn) {
1164 | Ok(id) => {
1165 let params = backend::params![id, identifier, source_bucket, title, downloaded_at, file_path];
1166 execute_insert(&self, conn, params)
1167 }
1168 | Err(why) => Err(why),
1169 },
1170 | Err(why) => Err(why),
1171 }
1172 }
1173 fn build_select_query(&self, base: &str) -> SelectQuery {
1174 Self::build_select_query(self, base)
1175 }
1176}
1177impl Row for ValidationRow {
1178 fn table(&self) -> Table {
1179 Table::ValidationHistory
1180 }
1181 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1182 let Self {
1183 path,
1184 check_type,
1185 success,
1186 message,
1187 checked_at,
1188 ..
1189 } = self.clone();
1190 let success = i32::from(success.unwrap_or(true));
1191 let checked_at = checked_at.unwrap_or_else(Utc::now).to_rfc3339();
1192 let required = required3(path, check_type, message);
1193 match required {
1194 | Ok((path, check_type, message)) => match self.next_row_id(conn) {
1195 | Ok(id) => {
1196 let params = backend::params![id, path, check_type, success, message, checked_at];
1197 execute_insert(&self, conn, params)
1198 }
1199 | Err(why) => Err(why),
1200 },
1201 | Err(why) => Err(why),
1202 }
1203 }
1204 fn build_select_query(&self, base: &str) -> SelectQuery {
1205 Self::build_select_query(self, base)
1206 }
1207}
1208impl Row for ModelRow {
1209 fn table(&self) -> Table {
1210 Table::Models
1211 }
1212 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1213 let Self {
1214 model_id,
1215 name,
1216 family,
1217 variant,
1218 version,
1219 attachment,
1220 open_weights,
1221 reasoning,
1222 structured_output,
1223 temperature,
1224 tool_call,
1225 parameters,
1226 release_date,
1227 knowledge,
1228 last_updated,
1229 limit_context,
1230 limit_output,
1231 limit_input,
1232 modality_input,
1233 modality_output,
1234 cost_input,
1235 cost_output,
1236 cost_cache_read,
1237 cost_cache_write,
1238 cost_reasoning,
1239 cost_input_audio,
1240 cost_output_audio,
1241 cost_over_200k,
1242 cost_tiers,
1243 benchmarks,
1244 weights,
1245 ..
1246 } = self.clone();
1247 let required = required1(model_id);
1248 match required {
1249 | Ok((model_id,)) => match self.next_row_id(conn) {
1250 | Ok(id) => {
1251 let params = backend::params![
1252 id,
1253 model_id,
1254 name,
1255 family,
1256 variant,
1257 version,
1258 attachment.map(i32::from),
1259 open_weights.map(i32::from),
1260 reasoning.map(i32::from),
1261 structured_output.map(i32::from),
1262 temperature.map(i32::from),
1263 tool_call.map(i32::from),
1264 parameters,
1265 release_date,
1266 knowledge,
1267 last_updated,
1268 limit_context,
1269 limit_output,
1270 limit_input,
1271 modality_input,
1272 modality_output,
1273 cost_input,
1274 cost_output,
1275 cost_cache_read,
1276 cost_cache_write,
1277 cost_reasoning,
1278 cost_input_audio,
1279 cost_output_audio,
1280 cost_over_200k,
1281 cost_tiers,
1282 benchmarks,
1283 weights,
1284 ];
1285 execute_insert(&self, conn, params)
1286 }
1287 | Err(why) => Err(why),
1288 },
1289 | Err(why) => Err(why),
1290 }
1291 }
1292 fn build_select_query(&self, base: &str) -> SelectQuery {
1293 Self::build_select_query(self, base)
1294 }
1295}
1296impl Row for ProviderRow {
1297 fn table(&self) -> Table {
1298 Table::Providers
1299 }
1300 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1301 let Self {
1302 provider_id,
1303 name,
1304 description,
1305 endpoint,
1306 documentation,
1307 authentication,
1308 env,
1309 npm,
1310 url,
1311 established_date,
1312 last_updated,
1313 ..
1314 } = self.clone();
1315 let required = required1(provider_id);
1316 match required {
1317 | Ok((provider_id,)) => match self.next_row_id(conn) {
1318 | Ok(id) => {
1319 let params = backend::params![
1320 id,
1321 provider_id,
1322 name,
1323 description,
1324 endpoint,
1325 documentation,
1326 authentication,
1327 env,
1328 npm,
1329 url,
1330 established_date,
1331 last_updated,
1332 ];
1333 execute_insert(&self, conn, params)
1334 }
1335 | Err(why) => Err(why),
1336 },
1337 | Err(why) => Err(why),
1338 }
1339 }
1340 fn build_select_query(&self, base: &str) -> SelectQuery {
1341 Self::build_select_query(self, base)
1342 }
1343}
1344impl LinkCacheRow {
1345 pub fn is_expired(&self) -> bool {
1347 self.expires_at.map(|e| Utc::now() > e).unwrap_or(true)
1348 }
1349}
1350fn display_or_none<T>(value: Option<&T>) -> String
1351where
1352 T: fmt::Display,
1353{
1354 value.map(ToString::to_string).unwrap_or_else(|| "None".to_string())
1355}
1356fn execute_insert<R, P>(row: &R, conn: &Connection, params: P) -> ApiResult<usize>
1357where
1358 R: Row,
1359 P: Params,
1360{
1361 let table_name = row.table().name();
1362 let table_columns = <R as Row>::fields();
1363 let fields = table_columns.join(", ");
1364 let placeholders = table_columns.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
1365 let query = format!("INSERT INTO {table_name} ({fields}) VALUES ({placeholders})");
1366 conn.execute(&query, params)
1367 .map_err(|why| eyre!("=> {} Failed to insert row: {why}", Label::fail()))
1368}
1369fn parse_rfc3339_column(row: &BackendRow<'_>, index: usize) -> Option<DateTime<Utc>> {
1370 row.get::<_, String>(index)
1371 .ok()
1372 .and_then(|value| DateTime::parse_from_rfc3339(&value).ok())
1373 .map(|value| value.with_timezone(&Utc))
1374}
1375fn write_optional_field<T>(f: &mut fmt::Formatter<'_>, label: &str, value: Option<&T>) -> fmt::Result
1376where
1377 T: fmt::Display,
1378{
1379 match value {
1380 | Some(value) => write!(f, ", {label}: {value}"),
1381 | None => Ok(()),
1382 }
1383}
1384fn write_optional_timestamp(f: &mut fmt::Formatter<'_>, label: &str, value: Option<&DateTime<Utc>>) -> fmt::Result {
1385 match value {
1386 | Some(value) => write!(f, ", {label}: {}", value.format("%Y-%m-%d %H:%M:%S")),
1387 | None => Ok(()),
1388 }
1389}
1390
1391#[cfg(feature = "duckdb")]
1392fn id_column_definition() -> &'static str {
1393 "id BIGINT PRIMARY KEY"
1394}
1395
1396#[cfg(not(feature = "duckdb"))]
1397fn id_column_definition() -> &'static str {
1398 "id INTEGER PRIMARY KEY AUTOINCREMENT"
1399}