1use super::backend::{self, BackendRow, BackendRowExt, 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::schema::agent::Weights;
10use crate::util::{print_values_as_table, to_rfc3339, Label};
11use acorn_macros::DatabaseRow;
12use async_trait::async_trait;
13use bon::Builder;
14use color_eyre::eyre::eyre;
15use core::fmt;
16use jiff::Timestamp;
17use serde::{Deserialize, Serialize};
18
19define_required_fn!(1, required1, A, a);
20define_required_fn!(3, required3, A, a, B, b, C, c);
21define_required_fn!(4, required4, A, a, B, b, C, c, D, d);
22define_required_fn!(5, required5, A, a, B, b, C, c, D, d, E, e);
23#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
25#[repr(i32)]
26pub enum Table {
27 Activity = 0,
29 Catalog = 1,
31 Licenses = 2,
33 LinkCache = 3,
35 ValidationHistory = 4,
37 ResearchActivities = 5,
39 ProgrammingLanguages = 6,
41 Models = 7,
43 Providers = 8,
45 Discoveries = 9,
47}
48#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
50#[builder(start_fn = init, on(String, into))]
51#[row(table = activity, order_by = "executed_at DESC")]
52pub struct ActivityRow {
53 pub id: Option<i64>,
55 pub command: Option<String>,
57 pub executed_at: Option<Timestamp>,
59 pub user_path: Option<String>,
61 pub success: Option<bool>,
63}
64#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
66#[builder(start_fn = init, on(String, into))]
67#[row(table = catalog, order_by = "title")]
68pub struct CatalogRow {
69 pub id: Option<i64>,
71 pub bucket_name: Option<String>,
73 pub bucket_url: Option<String>,
75 pub identifier: Option<String>,
77 pub title: Option<String>,
79 pub updated_at: Option<Timestamp>,
81}
82#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
84#[builder(start_fn = init, on(String, into))]
85#[row(table = discoveries, order_by = "discovered_at DESC")]
86pub struct IdentifierRow {
87 pub id: Option<i64>,
89 pub discovered_at: Option<Timestamp>,
91 pub identifier: Option<String>,
93 pub identifier_type: Option<String>,
95 pub metadata: Option<String>,
97 pub resolution_status: Option<String>,
99 pub source: Option<String>,
101 pub source_format: Option<String>,
103}
104#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
106#[builder(start_fn = init, on(String, into))]
107#[row(table = licenses)]
108pub struct LicenseRow {
109 pub id: Option<i64>,
111 pub identifier: Option<String>,
113 pub name: Option<String>,
115 pub is_deprecated: Option<bool>,
117 pub is_free_software: Option<bool>,
119 pub is_open_source: Option<bool>,
121}
122#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
124#[builder(start_fn = init, on(String, into))]
125#[row(table = programming_languages, order_by = "name")]
126pub struct ProgrammingLanguageRow {
127 pub id: Option<i64>,
129 pub language_id: Option<i64>,
131 pub name: Option<String>,
133 pub language_type: Option<String>,
135 pub color: Option<String>,
137 pub group_name: Option<String>,
139}
140#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
142#[builder(start_fn = init, on(String, into))]
143#[row(table = models)]
144pub struct ModelRow {
145 pub id: Option<i64>,
147 pub model_id: Option<String>,
149 pub name: Option<String>,
151 pub family: Option<String>,
153 pub variant: Option<String>,
155 pub version: Option<String>,
157 pub attachment: Option<bool>,
159 pub open_weights: Option<bool>,
161 pub reasoning: Option<bool>,
163 pub structured_output: Option<bool>,
165 pub temperature: Option<bool>,
167 pub tool_call: Option<bool>,
169 pub parameters: Option<i64>,
171 pub release_date: Option<String>,
173 pub knowledge: Option<String>,
175 pub last_updated: Option<String>,
177 pub limit_context: Option<i64>,
179 pub limit_output: Option<i64>,
181 pub limit_input: Option<i64>,
183 pub modality_input: Option<String>,
185 pub modality_output: Option<String>,
187 pub cost_input: Option<f64>,
189 pub cost_output: Option<f64>,
191 pub cost_cache_read: Option<f64>,
193 pub cost_cache_write: Option<f64>,
195 pub cost_reasoning: Option<f64>,
197 pub cost_input_audio: Option<f64>,
199 pub cost_output_audio: Option<f64>,
201 pub cost_over_200k: Option<String>,
203 pub cost_tiers: Option<String>,
205 pub benchmarks: Option<String>,
207 pub weights: Option<String>,
209}
210#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
212#[builder(start_fn = init, on(String, into))]
213#[row(table = providers)]
214pub struct ProviderRow {
215 pub id: Option<i64>,
217 pub provider_id: Option<String>,
219 pub name: Option<String>,
221 pub description: Option<String>,
223 pub endpoint: Option<String>,
225 pub documentation: Option<String>,
227 pub authentication: Option<String>,
229 pub env: Option<String>,
231 pub npm: Option<String>,
233 pub url: Option<String>,
235 pub established_date: Option<String>,
237 pub last_updated: Option<String>,
239 pub models: Option<String>,
241}
242#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
244#[builder(start_fn = init, on(String, into))]
245#[row(table = link_cache)]
246pub struct LinkCacheRow {
247 pub id: Option<i64>,
249 pub url: Option<String>,
251 pub status_code: Option<i32>,
253 pub is_reachable: Option<bool>,
255 pub checked_at: Option<Timestamp>,
257 pub expires_at: Option<Timestamp>,
259}
260#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
262#[builder(start_fn = init, on(String, into))]
263#[row(table = research_activities, order_by = "updated_at DESC")]
264pub struct ResearchActivityRow {
265 pub id: Option<i64>,
267 pub iid: Option<String>,
269 pub rad_json: Option<String>,
271 pub identity_keys_json: Option<String>,
273 pub provenance_json: Option<String>,
275 pub created_at: Option<Timestamp>,
277 pub updated_at: Option<Timestamp>,
279}
280#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
282#[builder(start_fn = init, on(String, into))]
283#[row(table = validation_history, order_by = "checked_at DESC")]
284pub struct ValidationRow {
285 pub id: Option<i64>,
287 pub path: Option<String>,
289 pub check_type: Option<String>,
291 pub success: Option<bool>,
293 pub message: Option<String>,
295 pub checked_at: Option<Timestamp>,
297}
298impl From<&str> for Table {
299 fn from(value: &str) -> Self {
300 let normalized = value.trim().to_ascii_lowercase();
301 match normalized.as_str() {
302 | "catalog" => Self::Catalog,
303 | "discoveries" | "discovery" => Self::Discoveries,
304 | "licenses" | "license" => Self::Licenses,
305 | "link_cache" | "linkcache" => Self::LinkCache,
306 | "validation_history" | "validationhistory" => Self::ValidationHistory,
307 | "research_activities" | "researchactivities" => Self::ResearchActivities,
308 | "programming_languages" | "programminglanguages" | "languages" | "language" => Self::ProgrammingLanguages,
309 | "models" | "model" => Self::Models,
310 | "providers" | "provider" => Self::Providers,
311 | _ => Self::Activity,
312 }
313 }
314}
315impl From<&BackendRow<'_>> for ModelRow {
316 fn from(row: &BackendRow<'_>) -> Self {
317 ModelRow {
318 id: row.get(0).ok(),
319 model_id: row.get(1).ok(),
320 name: row.get(2).ok(),
321 family: row.get(3).ok(),
322 variant: row.get(4).ok(),
323 version: row.get(5).ok(),
324 attachment: row.get::<_, i32>(6).ok().map(|value| value != 0),
325 open_weights: row.get::<_, i32>(7).ok().map(|value| value != 0),
326 reasoning: row.get::<_, i32>(8).ok().map(|value| value != 0),
327 structured_output: row.get::<_, i32>(9).ok().map(|value| value != 0),
328 temperature: row.get::<_, i32>(10).ok().map(|value| value != 0),
329 tool_call: row.get::<_, i32>(11).ok().map(|value| value != 0),
330 parameters: row.get(12).ok(),
331 release_date: row.get(13).ok(),
332 knowledge: row.get(14).ok(),
333 last_updated: row.get(15).ok(),
334 limit_context: row.get(16).ok(),
335 limit_output: row.get(17).ok(),
336 limit_input: row.get(18).ok(),
337 modality_input: row.get(19).ok(),
338 modality_output: row.get(20).ok(),
339 cost_input: row.get(21).ok(),
340 cost_output: row.get(22).ok(),
341 cost_cache_read: row.get(23).ok(),
342 cost_cache_write: row.get(24).ok(),
343 cost_reasoning: row.get(25).ok(),
344 cost_input_audio: row.get(26).ok(),
345 cost_output_audio: row.get(27).ok(),
346 cost_over_200k: row.get(28).ok(),
347 cost_tiers: row.get(29).ok(),
348 benchmarks: row.get(30).ok(),
349 weights: row.get(31).ok(),
350 }
351 }
352}
353impl From<BackendRow<'_>> for ModelRow {
354 fn from(row: BackendRow<'_>) -> Self {
355 ModelRow::from(&row)
356 }
357}
358impl From<&BackendRow<'_>> for ProviderRow {
359 fn from(row: &BackendRow<'_>) -> Self {
360 ProviderRow {
361 id: row.get(0).ok(),
362 provider_id: row.get(1).ok(),
363 name: row.get(2).ok(),
364 description: row.get(3).ok(),
365 endpoint: row.get(4).ok(),
366 documentation: row.get(5).ok(),
367 authentication: row.get(6).ok(),
368 env: row.get(7).ok(),
369 npm: row.get(8).ok(),
370 url: row.get(9).ok(),
371 established_date: row.get(10).ok(),
372 last_updated: row.get(11).ok(),
373 models: row.get(12).ok(),
374 }
375 }
376}
377impl From<BackendRow<'_>> for ProviderRow {
378 fn from(row: BackendRow<'_>) -> Self {
379 ProviderRow::from(&row)
380 }
381}
382impl From<&BackendRow<'_>> for ActivityRow {
383 fn from(row: &BackendRow<'_>) -> Self {
384 ActivityRow {
385 id: row.get(0).ok(),
386 command: row.get(1).ok(),
387 executed_at: row.parse_rfc3339(2),
388 user_path: row.get(3).ok(),
389 success: row.get::<_, i32>(4).ok().map(|value| value != 0),
390 }
391 }
392}
393impl From<BackendRow<'_>> for ActivityRow {
394 fn from(row: BackendRow<'_>) -> Self {
395 ActivityRow::from(&row)
396 }
397}
398impl From<&BackendRow<'_>> for IdentifierRow {
399 fn from(row: &BackendRow<'_>) -> Self {
400 IdentifierRow {
401 id: row.get(0).ok(),
402 discovered_at: row.parse_rfc3339(1),
403 identifier: row.get(2).ok(),
404 identifier_type: row.get(3).ok(),
405 metadata: row.get(4).ok(),
406 resolution_status: row.get(5).ok(),
407 source: row.get(6).ok(),
408 source_format: row.get(7).ok(),
409 }
410 }
411}
412impl From<BackendRow<'_>> for IdentifierRow {
413 fn from(row: BackendRow<'_>) -> Self {
414 IdentifierRow::from(&row)
415 }
416}
417impl From<&BackendRow<'_>> for LicenseRow {
418 fn from(row: &BackendRow<'_>) -> Self {
419 LicenseRow {
420 id: row.get(0).ok(),
421 identifier: row.get(1).ok(),
422 name: row.get(2).ok(),
423 is_deprecated: row.get::<_, i32>(3).ok().map(|value| value != 0),
424 is_free_software: row.get::<_, i32>(4).ok().map(|value| value != 0),
425 is_open_source: row.get::<_, i32>(5).ok().map(|value| value != 0),
426 }
427 }
428}
429impl From<BackendRow<'_>> for LicenseRow {
430 fn from(row: BackendRow<'_>) -> Self {
431 LicenseRow::from(&row)
432 }
433}
434impl From<&BackendRow<'_>> for ProgrammingLanguageRow {
435 fn from(row: &BackendRow<'_>) -> Self {
436 ProgrammingLanguageRow {
437 id: row.get(0).ok(),
438 language_id: row.get(1).ok(),
439 name: row.get(2).ok(),
440 language_type: row.get(3).ok(),
441 color: row.get(4).ok(),
442 group_name: row.get(5).ok(),
443 }
444 }
445}
446impl From<BackendRow<'_>> for ProgrammingLanguageRow {
447 fn from(row: BackendRow<'_>) -> Self {
448 ProgrammingLanguageRow::from(&row)
449 }
450}
451impl From<&BackendRow<'_>> for CatalogRow {
452 fn from(row: &BackendRow<'_>) -> Self {
453 CatalogRow {
454 id: row.get(0).ok(),
455 bucket_name: row.get(1).ok(),
456 bucket_url: row.get(2).ok(),
457 identifier: row.get(3).ok(),
458 title: row.get(4).ok(),
459 updated_at: row.parse_rfc3339(5),
460 }
461 }
462}
463impl From<BackendRow<'_>> for CatalogRow {
464 fn from(row: BackendRow<'_>) -> Self {
465 CatalogRow::from(&row)
466 }
467}
468impl From<&BackendRow<'_>> for LinkCacheRow {
469 fn from(row: &BackendRow<'_>) -> Self {
470 LinkCacheRow {
471 id: row.get(0).ok(),
472 url: row.get(1).ok(),
473 status_code: row.get(2).ok(),
474 is_reachable: row.get::<_, i32>(3).ok().map(|value| value != 0),
475 checked_at: row.parse_rfc3339(4),
476 expires_at: row.parse_rfc3339(5),
477 }
478 }
479}
480impl From<BackendRow<'_>> for LinkCacheRow {
481 fn from(row: BackendRow<'_>) -> Self {
482 LinkCacheRow::from(&row)
483 }
484}
485impl From<&BackendRow<'_>> for ResearchActivityRow {
486 fn from(row: &BackendRow<'_>) -> Self {
487 ResearchActivityRow {
488 id: row.get(0).ok(),
489 iid: row.get(1).ok(),
490 rad_json: row.get(2).ok(),
491 identity_keys_json: row.get(3).ok(),
492 provenance_json: row.get(4).ok(),
493 created_at: row.parse_rfc3339(5),
494 updated_at: row.parse_rfc3339(6),
495 }
496 }
497}
498impl From<BackendRow<'_>> for ResearchActivityRow {
499 fn from(row: BackendRow<'_>) -> Self {
500 ResearchActivityRow::from(&row)
501 }
502}
503impl From<&BackendRow<'_>> for ValidationRow {
504 fn from(row: &BackendRow<'_>) -> Self {
505 ValidationRow {
506 id: row.get(0).ok(),
507 path: row.get(1).ok(),
508 check_type: row.get(2).ok(),
509 success: row.get::<_, i32>(3).ok().map(|value| value != 0),
510 message: row.get(4).ok(),
511 checked_at: row.parse_rfc3339(5),
512 }
513 }
514}
515impl From<BackendRow<'_>> for ValidationRow {
516 fn from(row: BackendRow<'_>) -> Self {
517 ValidationRow::from(&row)
518 }
519}
520impl From<ActivityRow> for Vec<String> {
521 fn from(row: ActivityRow) -> Self {
522 vec![
523 row.id.map_or_else(String::new, |v| v.to_string()),
524 row.command.unwrap_or_default(),
525 row.executed_at.map_or_else(String::new, to_rfc3339),
526 row.user_path.unwrap_or_default(),
527 row.success.map_or_else(String::new, |v| v.to_string()),
528 ]
529 }
530}
531impl From<CatalogRow> for Vec<String> {
532 fn from(row: CatalogRow) -> Self {
533 vec![
534 row.id.map_or_else(String::new, |v| v.to_string()),
535 row.bucket_name.unwrap_or_default(),
536 row.bucket_url.unwrap_or_default(),
537 row.identifier.unwrap_or_default(),
538 row.title.unwrap_or_default(),
539 row.updated_at.map_or_else(String::new, to_rfc3339),
540 ]
541 }
542}
543impl From<IdentifierRow> for Vec<String> {
544 fn from(row: IdentifierRow) -> Self {
545 vec![
546 row.id.map_or_else(String::new, |value| value.to_string()),
547 row.discovered_at.map_or_else(String::new, to_rfc3339),
548 row.identifier.unwrap_or_default(),
549 row.identifier_type.unwrap_or_default(),
550 row.metadata.unwrap_or_default(),
551 row.resolution_status.unwrap_or_default(),
552 row.source.unwrap_or_default(),
553 row.source_format.unwrap_or_default(),
554 ]
555 }
556}
557impl From<LicenseRow> for Vec<String> {
558 fn from(row: LicenseRow) -> Self {
559 vec![
560 row.id.map_or_else(String::new, |v| v.to_string()),
561 row.identifier.unwrap_or_default(),
562 row.name.unwrap_or_default(),
563 row.is_deprecated.map_or_else(String::new, |v| v.to_string()),
564 row.is_free_software.map_or_else(String::new, |v| v.to_string()),
565 row.is_open_source.map_or_else(String::new, |v| v.to_string()),
566 ]
567 }
568}
569impl From<ProgrammingLanguageRow> for Vec<String> {
570 fn from(row: ProgrammingLanguageRow) -> Self {
571 vec![
572 row.id.map_or_else(String::new, |v| v.to_string()),
573 row.language_id.map_or_else(String::new, |v| v.to_string()),
574 row.name.unwrap_or_default(),
575 row.language_type.unwrap_or_default(),
576 row.color.unwrap_or_default(),
577 row.group_name.unwrap_or_default(),
578 ]
579 }
580}
581impl From<LinkCacheRow> for Vec<String> {
582 fn from(row: LinkCacheRow) -> Self {
583 vec![
584 row.id.map_or_else(String::new, |v| v.to_string()),
585 row.url.unwrap_or_default(),
586 row.status_code.map_or_else(String::new, |v| v.to_string()),
587 row.is_reachable.map_or_else(String::new, |v| v.to_string()),
588 row.checked_at.map_or_else(String::new, to_rfc3339),
589 row.expires_at.map_or_else(String::new, to_rfc3339),
590 ]
591 }
592}
593impl From<ResearchActivityRow> for Vec<String> {
594 fn from(row: ResearchActivityRow) -> Self {
595 vec![
596 row.id.map_or_else(String::new, |v| v.to_string()),
597 row.iid.unwrap_or_default(),
598 row.rad_json.unwrap_or_default(),
599 row.identity_keys_json.unwrap_or_default(),
600 row.provenance_json.unwrap_or_default(),
601 row.created_at.map_or_else(String::new, to_rfc3339),
602 row.updated_at.map_or_else(String::new, to_rfc3339),
603 ]
604 }
605}
606impl From<ValidationRow> for Vec<String> {
607 fn from(row: ValidationRow) -> Self {
608 vec![
609 row.id.map_or_else(String::new, |v| v.to_string()),
610 row.path.unwrap_or_default(),
611 row.check_type.unwrap_or_default(),
612 row.success.map_or_else(String::new, |v| v.to_string()),
613 row.message.unwrap_or_default(),
614 row.checked_at.map_or_else(String::new, to_rfc3339),
615 ]
616 }
617}
618impl From<ModelRow> for Vec<String> {
619 fn from(row: ModelRow) -> Self {
620 vec![
621 row.id.map_or_else(String::new, |v| v.to_string()),
622 row.model_id.unwrap_or_default(),
623 row.name.unwrap_or_default(),
624 row.family.unwrap_or_default(),
625 row.variant.unwrap_or_default(),
626 row.version.unwrap_or_default(),
627 row.attachment.map_or_else(String::new, |v| v.to_string()),
628 row.open_weights.map_or_else(String::new, |v| v.to_string()),
629 row.reasoning.map_or_else(String::new, |v| v.to_string()),
630 row.structured_output.map_or_else(String::new, |v| v.to_string()),
631 row.temperature.map_or_else(String::new, |v| v.to_string()),
632 row.tool_call.map_or_else(String::new, |v| v.to_string()),
633 row.parameters.map_or_else(String::new, |v| v.to_string()),
634 row.release_date.unwrap_or_default(),
635 row.knowledge.unwrap_or_default(),
636 row.last_updated.unwrap_or_default(),
637 row.limit_context.map_or_else(String::new, |v| v.to_string()),
638 row.limit_output.map_or_else(String::new, |v| v.to_string()),
639 row.limit_input.map_or_else(String::new, |v| v.to_string()),
640 row.modality_input.unwrap_or_default(),
641 row.modality_output.unwrap_or_default(),
642 row.cost_input.map_or_else(String::new, |v| v.to_string()),
643 row.cost_output.map_or_else(String::new, |v| v.to_string()),
644 row.cost_cache_read.map_or_else(String::new, |v| v.to_string()),
645 row.cost_cache_write.map_or_else(String::new, |v| v.to_string()),
646 row.cost_reasoning.map_or_else(String::new, |v| v.to_string()),
647 row.cost_input_audio.map_or_else(String::new, |v| v.to_string()),
648 row.cost_output_audio.map_or_else(String::new, |v| v.to_string()),
649 row.cost_over_200k.unwrap_or_default(),
650 row.cost_tiers.unwrap_or_default(),
651 row.benchmarks.unwrap_or_default(),
652 row.weights.unwrap_or_default(),
653 ]
654 }
655}
656impl From<ProviderRow> for Vec<String> {
657 fn from(row: ProviderRow) -> Self {
658 vec![
659 row.id.map_or_else(String::new, |v| v.to_string()),
660 row.provider_id.unwrap_or_default(),
661 row.name.unwrap_or_default(),
662 row.description.unwrap_or_default(),
663 row.endpoint.unwrap_or_default(),
664 row.documentation.unwrap_or_default(),
665 row.authentication.unwrap_or_default(),
666 row.env.unwrap_or_default(),
667 row.npm.unwrap_or_default(),
668 row.url.unwrap_or_default(),
669 row.established_date.unwrap_or_default(),
670 row.last_updated.unwrap_or_default(),
671 row.models.unwrap_or_default(),
672 ]
673 }
674}
675impl fmt::Display for ActivityRow {
676 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
677 write!(
678 f,
679 "ActivityRow = id: {}, command: {}, success: {}",
680 display_or_none(self.id.as_ref()),
681 display_or_none(self.command.as_ref()),
682 self.success.unwrap_or(false),
683 )
684 .and_then(|_| write_optional_field(f, "path", self.user_path.as_ref()))
685 .and_then(|_| write_optional_timestamp(f, "executed_at", self.executed_at.as_ref()))
686 }
687}
688impl fmt::Display for LicenseRow {
689 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
690 write!(
691 f,
692 "LicenseRow = id: {}, identifier: {}, name: {}, deprecated: {}, free: {}, osi: {}",
693 display_or_none(self.id.as_ref()),
694 display_or_none(self.identifier.as_ref()),
695 display_or_none(self.name.as_ref()),
696 self.is_deprecated.unwrap_or(false),
697 self.is_free_software.unwrap_or(false),
698 self.is_open_source.unwrap_or(false),
699 )
700 }
701}
702impl fmt::Display for CatalogRow {
703 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
704 write!(
705 f,
706 "CatalogRow = id: {}, bucket: {}, identifier: {}, title: {}",
707 display_or_none(self.id.as_ref()),
708 display_or_none(self.bucket_name.as_ref()),
709 display_or_none(self.identifier.as_ref()),
710 display_or_none(self.title.as_ref())
711 )
712 .and_then(|_| write_optional_timestamp(f, "updated_at", self.updated_at.as_ref()))
713 }
714}
715impl fmt::Display for IdentifierRow {
716 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
717 write!(
718 f,
719 "IdentifierRow = id: {}, identifier: {}, type: {}, source: {}",
720 display_or_none(self.id.as_ref()),
721 display_or_none(self.identifier.as_ref()),
722 display_or_none(self.identifier_type.as_ref()),
723 display_or_none(self.source.as_ref())
724 )
725 .and_then(|_| write_optional_timestamp(f, "discovered_at", self.discovered_at.as_ref()))
726 }
727}
728impl fmt::Display for ProgrammingLanguageRow {
729 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
730 write!(
731 f,
732 "ProgrammingLanguageRow = id: {}, language_id: {}, name: {}, type: {}",
733 display_or_none(self.id.as_ref()),
734 display_or_none(self.language_id.as_ref()),
735 display_or_none(self.name.as_ref()),
736 display_or_none(self.language_type.as_ref())
737 )
738 .and_then(|_| write_optional_field(f, "color", self.color.as_ref()))
739 .and_then(|_| write_optional_field(f, "group_name", self.group_name.as_ref()))
740 }
741}
742impl fmt::Display for LinkCacheRow {
743 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
744 write!(
745 f,
746 "LinkCacheRow = id: {}, url: {}, status: {}, reachable: {}",
747 display_or_none(self.id.as_ref()),
748 display_or_none(self.url.as_ref()),
749 self.status_code.unwrap_or(0),
750 self.is_reachable.unwrap_or(false)
751 )
752 .and_then(|_| write_optional_timestamp(f, "checked_at", self.checked_at.as_ref()))
753 .and_then(|_| write_optional_timestamp(f, "expires_at", self.expires_at.as_ref()))
754 }
755}
756impl fmt::Display for ResearchActivityRow {
757 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
758 write!(
759 f,
760 "ResearchActivityRow = id: {}, iid: {}",
761 display_or_none(self.id.as_ref()),
762 display_or_none(self.iid.as_ref())
763 )
764 .and_then(|_| write_optional_timestamp(f, "created_at", self.created_at.as_ref()))
765 .and_then(|_| write_optional_timestamp(f, "updated_at", self.updated_at.as_ref()))
766 }
767}
768impl fmt::Display for ValidationRow {
769 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
770 write!(
771 f,
772 "ValidationRow = id: {}, path: {}, type: {}, success: {}, message: {}",
773 display_or_none(self.id.as_ref()),
774 display_or_none(self.path.as_ref()),
775 display_or_none(self.check_type.as_ref()),
776 self.success.unwrap_or(false),
777 display_or_none(self.message.as_ref())
778 )
779 .and_then(|_| write_optional_timestamp(f, "checked_at", self.checked_at.as_ref()))
780 }
781}
782impl fmt::Display for ModelRow {
783 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
784 write!(
785 f,
786 "ModelRow = id: {}, model_id: {}, name: {}, family: {}",
787 display_or_none(self.id.as_ref()),
788 display_or_none(self.model_id.as_ref()),
789 display_or_none(self.name.as_ref()),
790 display_or_none(self.family.as_ref())
791 )
792 }
793}
794impl ModelRow {
795 pub fn parsed_weights(&self) -> Option<Weights> {
797 self.weights.as_deref().and_then(|weights| serde_json::from_str(weights).ok())
798 }
799 pub fn update_weights(&self, path: Option<PathBuf>) -> ApiResult<usize> {
801 match (self.model_id.as_deref(), self.weights.as_deref()) {
802 | (Some(model_id), Some(weights)) => Database::<Table>::from_path(path).with_connection(|conn| {
803 conn.execute("UPDATE models SET weights = ? WHERE model_id = ?", backend::params![weights, model_id])
804 .map_err(|why| eyre!("=> {} Failed to update model weights: {why}", Label::fail()))
805 }),
806 | (None, _) => Err(eyre!("Model identifier is required to update model weights")),
807 | (_, None) => Err(eyre!("Model weights are required to update model weights")),
808 }
809 }
810}
811impl fmt::Display for ProviderRow {
812 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
813 write!(
814 f,
815 "ProviderRow = id: {}, provider_id: {}, name: {}",
816 display_or_none(self.id.as_ref()),
817 display_or_none(self.provider_id.as_ref()),
818 display_or_none(self.name.as_ref())
819 )
820 .and_then(|_| write_optional_field(f, "description", self.description.as_ref()))
821 }
822}
823#[async_trait]
824impl TableSchemaProvider for Table {
825 fn all() -> &'static [Self] {
826 &[
827 Table::Activity,
828 Table::Catalog,
829 Table::Discoveries,
830 Table::Licenses,
831 Table::LinkCache,
832 Table::ValidationHistory,
833 Table::ResearchActivities,
834 Table::ProgrammingLanguages,
835 Table::Models,
836 Table::Providers,
837 ]
838 }
839 fn create_statement(&self) -> String {
841 let columns = match self {
842 | Table::Activity => {
843 r#"
844 command TEXT NOT NULL,
845 executed_at TEXT NOT NULL,
846 user_path TEXT,
847 success INTEGER NOT NULL DEFAULT 1
848 "#
849 }
850 | Table::Catalog => {
851 r#"
852 bucket_name TEXT NOT NULL,
853 bucket_url TEXT NOT NULL,
854 identifier TEXT NOT NULL,
855 title TEXT NOT NULL,
856 updated_at TEXT NOT NULL,
857 UNIQUE(bucket_name, identifier)
858 "#
859 }
860 | Table::Discoveries => {
861 r#"
862 discovered_at TEXT NOT NULL,
863 identifier TEXT NOT NULL,
864 identifier_type TEXT NOT NULL,
865 metadata TEXT,
866 resolution_status TEXT NOT NULL,
867 source TEXT NOT NULL,
868 source_format TEXT NOT NULL
869 "#
870 }
871 | Table::Licenses => {
872 r#"
873 identifier TEXT NOT NULL UNIQUE,
874 name TEXT NOT NULL,
875 is_deprecated INTEGER NOT NULL,
876 is_free_software INTEGER NOT NULL,
877 is_open_source INTEGER NOT NULL
878 "#
879 }
880 | Table::LinkCache => {
881 r#"
882 url TEXT NOT NULL UNIQUE,
883 status_code INTEGER NOT NULL,
884 is_reachable INTEGER NOT NULL,
885 checked_at TEXT NOT NULL,
886 expires_at TEXT NOT NULL
887 "#
888 }
889 | Table::ValidationHistory => {
890 r#"
891 path TEXT NOT NULL,
892 check_type TEXT NOT NULL,
893 success INTEGER NOT NULL,
894 message TEXT NOT NULL,
895 checked_at TEXT NOT NULL
896 "#
897 }
898 | Table::ResearchActivities => {
899 r#"
900 iid TEXT NOT NULL UNIQUE,
901 rad_json TEXT NOT NULL,
902 identity_keys_json TEXT NOT NULL,
903 provenance_json TEXT NOT NULL,
904 created_at TEXT NOT NULL,
905 updated_at TEXT NOT NULL
906 "#
907 }
908 | Table::ProgrammingLanguages => {
909 r#"
910 language_id INTEGER,
911 name TEXT NOT NULL UNIQUE,
912 language_type TEXT,
913 color TEXT,
914 group_name TEXT
915 "#
916 }
917 | Table::Models => {
918 r#"
919 model_id TEXT NOT NULL UNIQUE,
920 name TEXT,
921 family TEXT,
922 variant TEXT,
923 version TEXT,
924 attachment INTEGER,
925 open_weights INTEGER,
926 reasoning INTEGER,
927 structured_output INTEGER,
928 temperature INTEGER,
929 tool_call INTEGER,
930 parameters INTEGER,
931 release_date TEXT,
932 knowledge TEXT,
933 last_updated TEXT,
934 limit_context INTEGER,
935 limit_output INTEGER,
936 limit_input INTEGER,
937 modality_input TEXT,
938 modality_output TEXT,
939 cost_input REAL,
940 cost_output REAL,
941 cost_cache_read REAL,
942 cost_cache_write REAL,
943 cost_reasoning REAL,
944 cost_input_audio REAL,
945 cost_output_audio REAL,
946 cost_over_200k TEXT,
947 cost_tiers TEXT,
948 benchmarks TEXT,
949 weights TEXT
950 "#
951 }
952 | Table::Providers => {
953 r#"
954 provider_id TEXT NOT NULL UNIQUE,
955 name TEXT,
956 description TEXT,
957 endpoint TEXT,
958 documentation TEXT,
959 authentication TEXT,
960 env TEXT,
961 npm TEXT,
962 url TEXT,
963 established_date TEXT,
964 last_updated TEXT,
965 models TEXT
966 "#
967 }
968 };
969
970 format!(
971 r#"
972 CREATE TABLE IF NOT EXISTS {} (
973 {},
974 {}
975 )
976 "#,
977 self.name(),
978 id_column_definition(),
979 columns.trim()
980 )
981 }
982 fn name(&self) -> &'static str {
984 match self {
985 | Table::Activity => "activity",
986 | Table::Catalog => "catalog",
987 | Table::Discoveries => "discoveries",
988 | Table::Licenses => "licenses",
989 | Table::LinkCache => "link_cache",
990 | Table::ValidationHistory => "validation_history",
991 | Table::ResearchActivities => "research_activities",
992 | Table::ProgrammingLanguages => "programming_languages",
993 | Table::Models => "models",
994 | Table::Providers => "providers",
995 }
996 }
997 async fn populate(&self, path: Option<PathBuf>) -> ApiResult<usize> {
998 match &self {
999 | Table::Licenses => {
1000 let progress = create_progress_bar(0, ProgressType::Spinner);
1001 progress.set_message("Downloading SPDX license data...");
1002 let response = api::spdx::download().await;
1003 let message = format!("{}SPDX metadata download complete", Label::CHECKMARK);
1004 finish_progress_bar(&progress, message);
1005 match response {
1006 | Ok(data) => data.persist(Database::<Self>::from_path(path.clone())).await,
1007 | Err(why) => Err(eyre!("Failed to download SPDX license data — {why}")),
1008 }
1009 }
1010 | Table::ProgrammingLanguages => {
1011 let progress = create_progress_bar(0, ProgressType::Spinner);
1012 progress.set_message("Downloading GitLab programming language data...");
1013 let response = api::gitlab::languages().await;
1014 let message = format!("{}GitLab programming language metadata download complete", Label::CHECKMARK);
1015 finish_progress_bar(&progress, message);
1016 match response {
1017 | Ok(data) => data.persist(Database::<Self>::from_path(path.clone())).await,
1018 | Err(why) => Err(eyre!("Failed to download GitLab programming language data — {why}")),
1019 }
1020 }
1021 | Table::Models => {
1022 let progress = create_progress_bar(0, ProgressType::Spinner);
1023 progress.set_message("Downloading models.dev catalog (for models)...");
1024 let response = api::models_dev::download_cached().await;
1025 let message = format!("{}Models.dev catalog download complete", Label::CHECKMARK);
1026 finish_progress_bar(&progress, message);
1027 match response {
1028 | Ok(catalog) => catalog.models().persist(Database::<Self>::from_path(path.clone())).await,
1029 | Err(why) => Err(eyre!("Failed to download models.dev catalog — {why}")),
1030 }
1031 }
1032 | Table::Providers => {
1033 let progress = create_progress_bar(0, ProgressType::Spinner);
1034 progress.set_message("Downloading models.dev catalog (for providers)...");
1035 let response = api::models_dev::download_cached().await;
1036 let message = format!("{}Models.dev catalog download complete", Label::CHECKMARK);
1037 finish_progress_bar(&progress, message);
1038 match response {
1039 | Ok(catalog) => catalog.providers().persist(Database::<Self>::from_path(path.clone())).await,
1040 | Err(why) => Err(eyre!("Failed to download models.dev catalog — {why}")),
1041 }
1042 }
1043 | table => Err(eyre!("populate() not implemented for {} table", table.name())),
1044 }
1045 }
1046 fn print(self, path: Option<PathBuf>) {
1054 fn print_rows<R>(table: Table, path: Option<&PathBuf>)
1055 where
1056 R: Into<Vec<String>> + Row + Default + fmt::Display,
1057 for<'row> R: From<&'row BackendRow<'row>>,
1058 {
1059 let row = R::default();
1060 let fields = <R as Row>::fields();
1061 let query = format!("SELECT {} FROM {}", fields.join(", "), table.name());
1062 let (query, params) = row.build_select_query(&query);
1063 let rows = table
1064 .rows::<R, _>(&query, params, path)
1065 .map(|rows| rows.into_iter().map(Into::into).collect::<Vec<Vec<String>>>())
1066 .unwrap_or_default();
1067 print_values_as_table::<String>(fields.to_vec(), rows, Some(table.name().to_string()));
1068 }
1069 match self {
1070 | Table::Activity => print_rows::<ActivityRow>(self, path.as_ref()),
1071 | Table::Catalog => print_rows::<CatalogRow>(self, path.as_ref()),
1072 | Table::Discoveries => print_rows::<IdentifierRow>(self, path.as_ref()),
1073 | Table::Licenses => print_rows::<LicenseRow>(self, path.as_ref()),
1074 | Table::ProgrammingLanguages => print_rows::<ProgrammingLanguageRow>(self, path.as_ref()),
1075 | Table::LinkCache => print_rows::<LinkCacheRow>(self, path.as_ref()),
1076 | Table::ValidationHistory => print_rows::<ValidationRow>(self, path.as_ref()),
1077 | Table::ResearchActivities => print_rows::<ResearchActivityRow>(self, path.as_ref()),
1078 | Table::Models => print_rows::<ModelRow>(self, path.as_ref()),
1079 | Table::Providers => print_rows::<ProviderRow>(self, path.as_ref()),
1080 }
1081 }
1082 fn rows<R, P>(&self, query: &str, params: P, path: Option<&PathBuf>) -> ApiResult<Vec<R>>
1084 where
1085 P: Params,
1086 for<'row> R: Row + From<&'row BackendRow<'row>>,
1087 {
1088 Database::<Table>::from_path(path.cloned()).with_connection(|conn| {
1089 conn.prepare(query)
1090 .map_err(|why| eyre!("=> {} Failed to prepare {} query: {why}", Label::fail(), self.name()))
1091 .and_then(|mut stmt| {
1092 stmt.query_map(params, |row| Ok(R::from(row)))
1093 .map_err(|why| eyre!("=> {} Failed to query {}: {why}", Label::fail(), self.name()))
1094 .and_then(|rows| {
1095 rows.collect::<core::result::Result<Vec<_>, _>>()
1096 .map_err(|why| eyre!("=> {} Failed to read {} rows: {why}", Label::fail(), self.name()))
1097 })
1098 })
1099 })
1100 }
1101}
1102impl Row for ActivityRow {
1103 fn table(&self) -> Table {
1104 Table::Activity
1105 }
1106 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1107 let Self {
1108 command,
1109 executed_at,
1110 user_path,
1111 success,
1112 ..
1113 } = self.clone();
1114 let executed_at = to_rfc3339(executed_at.unwrap_or_else(Timestamp::now));
1115 let success = i32::from(success.unwrap_or(true));
1116 let required = required1(command);
1117 match required {
1118 | Ok((command,)) => match self.next_row_id(conn) {
1119 | Ok(id) => {
1120 let params = backend::params![id, command, executed_at, user_path, success];
1121 execute_insert(&self, conn, params)
1122 }
1123 | Err(why) => Err(why),
1124 },
1125 | Err(why) => Err(why),
1126 }
1127 }
1128 fn build_select_query(&self, base: &str) -> SelectQuery {
1129 Self::build_select_query(self, base)
1130 }
1131}
1132impl Row for CatalogRow {
1133 fn table(&self) -> Table {
1134 Table::Catalog
1135 }
1136 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1137 let Self {
1138 bucket_name,
1139 bucket_url,
1140 identifier,
1141 title,
1142 updated_at,
1143 ..
1144 } = self.clone();
1145 let updated_at = to_rfc3339(updated_at.unwrap_or_else(Timestamp::now));
1146 let required = required4(bucket_name, bucket_url, identifier, title);
1147 match required {
1148 | Ok((bucket_name, bucket_url, identifier, title)) => match self.next_row_id(conn) {
1149 | Ok(id) => {
1150 let params = backend::params![id, bucket_name, bucket_url, identifier, title, updated_at];
1151 execute_insert(&self, conn, params)
1152 }
1153 | Err(why) => Err(why),
1154 },
1155 | Err(why) => Err(why),
1156 }
1157 }
1158 fn build_select_query(&self, base: &str) -> SelectQuery {
1159 Self::build_select_query(self, base)
1160 }
1161}
1162impl Row for IdentifierRow {
1163 fn table(&self) -> Table {
1164 Table::Discoveries
1165 }
1166 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1167 let Self {
1168 discovered_at,
1169 identifier,
1170 identifier_type,
1171 metadata,
1172 resolution_status,
1173 source,
1174 source_format,
1175 ..
1176 } = self.clone();
1177 let discovered_at = to_rfc3339(discovered_at.unwrap_or_else(Timestamp::now));
1178 match required1(identifier) {
1179 | Ok((identifier,)) => match self.next_row_id(conn) {
1180 | Ok(id) => execute_insert(
1181 &self,
1182 conn,
1183 backend::params![
1184 id,
1185 discovered_at,
1186 identifier,
1187 identifier_type.unwrap_or_else(|| "unknown".to_string()),
1188 metadata,
1189 resolution_status.unwrap_or_else(|| "not-requested".to_string()),
1190 source.unwrap_or_else(|| "unknown".to_string()),
1191 source_format.unwrap_or_else(|| "text".to_string()),
1192 ],
1193 ),
1194 | Err(why) => Err(why),
1195 },
1196 | Err(why) => Err(why),
1197 }
1198 }
1199 fn build_select_query(&self, base: &str) -> SelectQuery {
1200 Self::build_select_query(self, base)
1201 }
1202}
1203impl Row for LicenseRow {
1204 fn table(&self) -> Table {
1205 Table::Licenses
1206 }
1207 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1208 let Self {
1209 identifier,
1210 name,
1211 is_deprecated,
1212 is_free_software,
1213 is_open_source,
1214 ..
1215 } = self.clone();
1216 let required = required5(identifier, name, is_deprecated, is_free_software, is_open_source);
1217 match required {
1218 | Ok((identifier, name, is_deprecated, is_free_software, is_open_source)) => match self.next_row_id(conn) {
1219 | Ok(id) => {
1220 let params = backend::params![
1221 id,
1222 identifier,
1223 name,
1224 i32::from(is_deprecated),
1225 i32::from(is_free_software),
1226 i32::from(is_open_source)
1227 ];
1228 execute_insert(&self, conn, params)
1229 }
1230 | Err(why) => Err(why),
1231 },
1232 | Err(why) => Err(why),
1233 }
1234 }
1235 fn build_select_query(&self, base: &str) -> SelectQuery {
1236 Self::build_select_query(self, base)
1237 }
1238}
1239impl Row for LinkCacheRow {
1240 fn table(&self) -> Table {
1241 Table::LinkCache
1242 }
1243 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1244 let Self {
1245 url,
1246 status_code,
1247 is_reachable,
1248 checked_at,
1249 expires_at,
1250 ..
1251 } = self.clone();
1252 let checked_at = to_rfc3339(checked_at.unwrap_or_else(Timestamp::now));
1253 let required = required4(url, status_code, is_reachable, expires_at);
1254 match required {
1255 | Ok((url, status_code, is_reachable, expires_at)) => match self.next_row_id(conn) {
1256 | Ok(id) => {
1257 let params = backend::params![id, url, status_code, i32::from(is_reachable), checked_at, to_rfc3339(expires_at)];
1258 execute_insert(&self, conn, params)
1259 }
1260 | Err(why) => Err(why),
1261 },
1262 | Err(why) => Err(why),
1263 }
1264 }
1265 fn build_select_query(&self, base: &str) -> SelectQuery {
1266 Self::build_select_query(self, base)
1267 }
1268}
1269impl Row for ProgrammingLanguageRow {
1270 fn table(&self) -> Table {
1271 Table::ProgrammingLanguages
1272 }
1273 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1274 let Self {
1275 language_id,
1276 name,
1277 language_type,
1278 color,
1279 group_name,
1280 ..
1281 } = self.clone();
1282 let required = required1(name);
1283 match required {
1284 | Ok((name,)) => match self.next_row_id(conn) {
1285 | Ok(id) => {
1286 let params = backend::params![id, language_id, name, language_type, color, group_name];
1287 execute_insert(&self, conn, params)
1288 }
1289 | Err(why) => Err(why),
1290 },
1291 | Err(why) => Err(why),
1292 }
1293 }
1294 fn build_select_query(&self, base: &str) -> SelectQuery {
1295 Self::build_select_query(self, base)
1296 }
1297}
1298impl Row for ResearchActivityRow {
1299 fn table(&self) -> Table {
1300 Table::ResearchActivities
1301 }
1302 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1303 let Self {
1304 iid,
1305 rad_json,
1306 identity_keys_json,
1307 provenance_json,
1308 created_at,
1309 updated_at,
1310 ..
1311 } = self.clone();
1312 let created_at = to_rfc3339(created_at.unwrap_or_else(Timestamp::now));
1313 let updated_at = to_rfc3339(updated_at.unwrap_or_else(Timestamp::now));
1314 let required = required4(iid, rad_json, identity_keys_json, provenance_json);
1315 match required {
1316 | Ok((iid, rad_json, identity_keys_json, provenance_json)) => match self.next_row_id(conn) {
1317 | Ok(id) => {
1318 let params = backend::params![id, iid, rad_json, identity_keys_json, provenance_json, created_at, updated_at];
1319 execute_insert(&self, conn, params)
1320 }
1321 | Err(why) => Err(why),
1322 },
1323 | Err(why) => Err(why),
1324 }
1325 }
1326 fn build_select_query(&self, base: &str) -> SelectQuery {
1327 Self::build_select_query(self, base)
1328 }
1329}
1330impl Row for ValidationRow {
1331 fn table(&self) -> Table {
1332 Table::ValidationHistory
1333 }
1334 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1335 let Self {
1336 path,
1337 check_type,
1338 success,
1339 message,
1340 checked_at,
1341 ..
1342 } = self.clone();
1343 let success = i32::from(success.unwrap_or(true));
1344 let checked_at = to_rfc3339(checked_at.unwrap_or_else(Timestamp::now));
1345 let required = required3(path, check_type, message);
1346 match required {
1347 | Ok((path, check_type, message)) => match self.next_row_id(conn) {
1348 | Ok(id) => {
1349 let params = backend::params![id, path, check_type, success, message, checked_at];
1350 execute_insert(&self, conn, params)
1351 }
1352 | Err(why) => Err(why),
1353 },
1354 | Err(why) => Err(why),
1355 }
1356 }
1357 fn build_select_query(&self, base: &str) -> SelectQuery {
1358 Self::build_select_query(self, base)
1359 }
1360}
1361impl Row for ModelRow {
1362 fn table(&self) -> Table {
1363 Table::Models
1364 }
1365 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1366 let Self {
1367 model_id,
1368 name,
1369 family,
1370 variant,
1371 version,
1372 attachment,
1373 open_weights,
1374 reasoning,
1375 structured_output,
1376 temperature,
1377 tool_call,
1378 parameters,
1379 release_date,
1380 knowledge,
1381 last_updated,
1382 limit_context,
1383 limit_output,
1384 limit_input,
1385 modality_input,
1386 modality_output,
1387 cost_input,
1388 cost_output,
1389 cost_cache_read,
1390 cost_cache_write,
1391 cost_reasoning,
1392 cost_input_audio,
1393 cost_output_audio,
1394 cost_over_200k,
1395 cost_tiers,
1396 benchmarks,
1397 weights,
1398 ..
1399 } = self.clone();
1400 let required = required1(model_id);
1401 match required {
1402 | Ok((model_id,)) => match self.next_row_id(conn) {
1403 | Ok(id) => {
1404 let params = backend::params![
1405 id,
1406 model_id,
1407 name,
1408 family,
1409 variant,
1410 version,
1411 attachment.map(i32::from),
1412 open_weights.map(i32::from),
1413 reasoning.map(i32::from),
1414 structured_output.map(i32::from),
1415 temperature.map(i32::from),
1416 tool_call.map(i32::from),
1417 parameters,
1418 release_date,
1419 knowledge,
1420 last_updated,
1421 limit_context,
1422 limit_output,
1423 limit_input,
1424 modality_input,
1425 modality_output,
1426 cost_input,
1427 cost_output,
1428 cost_cache_read,
1429 cost_cache_write,
1430 cost_reasoning,
1431 cost_input_audio,
1432 cost_output_audio,
1433 cost_over_200k,
1434 cost_tiers,
1435 benchmarks,
1436 weights,
1437 ];
1438 execute_insert(&self, conn, params)
1439 }
1440 | Err(why) => Err(why),
1441 },
1442 | Err(why) => Err(why),
1443 }
1444 }
1445 fn build_select_query(&self, base: &str) -> SelectQuery {
1446 Self::build_select_query(self, base)
1447 }
1448}
1449impl Row for ProviderRow {
1450 fn table(&self) -> Table {
1451 Table::Providers
1452 }
1453 fn insert(self, conn: &Connection) -> ApiResult<usize> {
1454 let Self {
1455 provider_id,
1456 name,
1457 description,
1458 endpoint,
1459 documentation,
1460 authentication,
1461 env,
1462 npm,
1463 url,
1464 established_date,
1465 last_updated,
1466 models,
1467 ..
1468 } = self.clone();
1469 let required = required1(provider_id);
1470 match required {
1471 | Ok((provider_id,)) => match self.next_row_id(conn) {
1472 | Ok(id) => {
1473 let params = backend::params![
1474 id,
1475 provider_id,
1476 name,
1477 description,
1478 endpoint,
1479 documentation,
1480 authentication,
1481 env,
1482 npm,
1483 url,
1484 established_date,
1485 last_updated,
1486 models,
1487 ];
1488 execute_insert(&self, conn, params)
1489 }
1490 | Err(why) => Err(why),
1491 },
1492 | Err(why) => Err(why),
1493 }
1494 }
1495 fn build_select_query(&self, base: &str) -> SelectQuery {
1496 Self::build_select_query(self, base)
1497 }
1498}
1499impl LinkCacheRow {
1500 pub fn is_expired(&self) -> bool {
1502 self.expires_at.map(|expires_at| Timestamp::now() > expires_at).unwrap_or(true)
1503 }
1504}
1505fn display_or_none<T>(value: Option<&T>) -> String
1506where
1507 T: fmt::Display,
1508{
1509 value.map(ToString::to_string).unwrap_or_else(|| "None".to_string())
1510}
1511fn execute_insert<R, P>(row: &R, conn: &Connection, params: P) -> ApiResult<usize>
1512where
1513 R: Row,
1514 P: Params,
1515{
1516 let table_name = row.table().name();
1517 let table_columns = <R as Row>::fields();
1518 let fields = table_columns.join(", ");
1519 let placeholders = table_columns.iter().map(|_| "?").collect::<Vec<_>>().join(", ");
1520 let query = format!("INSERT INTO {table_name} ({fields}) VALUES ({placeholders})");
1521 conn.execute(&query, params)
1522 .map_err(|why| eyre!("=> {} Failed to insert row: {why}", Label::fail()))
1523}
1524fn write_optional_field<T>(f: &mut fmt::Formatter<'_>, label: &str, value: Option<&T>) -> fmt::Result
1525where
1526 T: fmt::Display,
1527{
1528 match value {
1529 | Some(value) => write!(f, ", {label}: {value}"),
1530 | None => Ok(()),
1531 }
1532}
1533fn write_optional_timestamp(f: &mut fmt::Formatter<'_>, label: &str, value: Option<&Timestamp>) -> fmt::Result {
1534 match value {
1535 | Some(value) => write!(f, ", {label}: {}", value.strftime("%Y-%m-%d %H:%M:%S")),
1536 | None => Ok(()),
1537 }
1538}
1539#[cfg(feature = "duckdb")]
1540fn id_column_definition() -> &'static str {
1541 "id BIGINT PRIMARY KEY"
1542}
1543
1544#[cfg(not(feature = "duckdb"))]
1545fn id_column_definition() -> &'static str {
1546 "id INTEGER PRIMARY KEY AUTOINCREMENT"
1547}