Skip to main content

acorn/io/database/
schema.rs

1//! Database schema model
2//!
3use 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/// Database tables
23#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
24#[repr(i32)]
25pub enum Table {
26    /// CLI activity log
27    Activity = 0,
28    /// Research activity index entries from buckets
29    Catalog = 1,
30    /// SPDX license data
31    Licenses = 2,
32    /// Cached link check results
33    LinkCache = 3,
34    /// Validation/check results history
35    ValidationHistory = 4,
36    /// Downloaded research activity snapshots
37    ResearchActivityCache = 5,
38    /// Programming language metadata cached from GitLab
39    ProgrammingLanguages = 6,
40    /// Models.dev model metadata
41    Models = 7,
42    /// Models.dev provider metadata
43    Providers = 8,
44}
45/// Activity log entry
46#[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    /// Unique identifier for the activity entry
51    pub id: Option<i64>,
52    /// Command that was executed
53    pub command: Option<String>,
54    /// When the command was executed
55    pub executed_at: Option<DateTime<Utc>>,
56    /// Path provided by user (if any)
57    pub user_path: Option<String>,
58    /// Whether the command succeeded
59    pub success: Option<bool>,
60}
61/// Catalog/index entry from bucket
62#[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    /// Unique identifier for the catalog entry
67    pub id: Option<i64>,
68    /// Name of the bucket
69    pub bucket_name: Option<String>,
70    /// URL of the bucket repository
71    pub bucket_url: Option<String>,
72    /// Research activity identifier
73    pub identifier: Option<String>,
74    /// Research activity title
75    pub title: Option<String>,
76    /// When the entry was last updated
77    pub updated_at: Option<DateTime<Utc>>,
78}
79/// SPDX license entry
80#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
81#[builder(start_fn = init, on(String, into))]
82#[row(table = licenses)]
83pub struct LicenseRow {
84    /// Unique identifier for the license entry
85    pub id: Option<i64>,
86    /// Unique license identifier (ex., "MIT")
87    pub identifier: Option<String>,
88    /// License name (ex., "MIT License")
89    pub name: Option<String>,
90    /// Specifies whether the entire license is deprecated
91    pub is_deprecated: Option<bool>,
92    /// Specifies whether the License is listed as free by the Free Software Foundation (FSF)
93    pub is_free_software: Option<bool>,
94    /// Specifies whether the license is approved by the Open Source Initiative (OSI)
95    pub is_open_source: Option<bool>,
96}
97/// Programming language metadata entry
98#[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    /// Unique identifier for the row
103    pub id: Option<i64>,
104    /// Stable language identifier from GitLab linguist metadata
105    pub language_id: Option<i64>,
106    /// Language display name
107    pub name: Option<String>,
108    /// Language category (for example, programming, markup, data, prose)
109    pub language_type: Option<String>,
110    /// Optional color associated with language
111    pub color: Option<String>,
112    /// Optional parent group name
113    pub group_name: Option<String>,
114}
115/// Models.dev model metadata entry
116#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
117#[builder(start_fn = init, on(String, into))]
118#[row(table = models)]
119pub struct ModelRow {
120    /// Unique identifier for the row
121    pub id: Option<i64>,
122    /// Unique model identifier from models.dev
123    pub model_id: Option<String>,
124    /// Model display name
125    pub name: Option<String>,
126    /// Model family (e.g., llama, gemma)
127    pub family: Option<String>,
128    /// Model variant (e.g., coder, instruct)
129    pub variant: Option<String>,
130    /// Model version string
131    pub version: Option<String>,
132    /// Whether the model supports file attachment
133    pub attachment: Option<bool>,
134    /// Whether the model weights are openly available
135    pub open_weights: Option<bool>,
136    /// Whether the model supports extended reasoning
137    pub reasoning: Option<bool>,
138    /// Whether the model supports structured output
139    pub structured_output: Option<bool>,
140    /// Whether the model supports temperature configuration
141    pub temperature: Option<bool>,
142    /// Whether the model supports tool calling
143    pub tool_call: Option<bool>,
144    /// Number of parameters in billions
145    pub parameters: Option<i64>,
146    /// Release date of the model
147    pub release_date: Option<String>,
148    /// Knowledge cutoff date
149    pub knowledge: Option<String>,
150    /// Date the model was last updated
151    pub last_updated: Option<String>,
152    /// Maximum context window size in tokens
153    pub limit_context: Option<i64>,
154    /// Maximum output token count
155    pub limit_output: Option<i64>,
156    /// Maximum input token count
157    pub limit_input: Option<i64>,
158    /// Input modalities supported (JSON array)
159    pub modality_input: Option<String>,
160    /// Output modalities supported (JSON array)
161    pub modality_output: Option<String>,
162    /// Cost per million input tokens
163    pub cost_input: Option<f64>,
164    /// Cost per million output tokens
165    pub cost_output: Option<f64>,
166    /// Cost per million cached input tokens
167    pub cost_cache_read: Option<f64>,
168    /// Cost per million cached write tokens
169    pub cost_cache_write: Option<f64>,
170    /// Extended reasoning cost per million tokens
171    pub cost_reasoning: Option<f64>,
172    /// Cost per million input audio tokens
173    pub cost_input_audio: Option<f64>,
174    /// Cost per million output audio tokens
175    pub cost_output_audio: Option<f64>,
176    /// Pricing for context windows exceeding 200K tokens (JSON)
177    pub cost_over_200k: Option<String>,
178    /// Pricing tiers (JSON array)
179    pub cost_tiers: Option<String>,
180    /// Benchmark evaluation results (JSON array)
181    pub benchmarks: Option<String>,
182    /// Download sources for model weights (JSON array)
183    pub weights: Option<String>,
184}
185/// Models.dev provider metadata entry
186#[derive(Builder, Clone, DatabaseRow, Debug, Default, Deserialize, Serialize)]
187#[builder(start_fn = init, on(String, into))]
188#[row(table = providers)]
189pub struct ProviderRow {
190    /// Unique identifier for the row
191    pub id: Option<i64>,
192    /// Unique provider identifier from models.dev
193    pub provider_id: Option<String>,
194    /// Provider display name
195    pub name: Option<String>,
196    /// Provider description
197    pub description: Option<String>,
198    /// API endpoint base URL
199    pub endpoint: Option<String>,
200    /// Documentation URL
201    pub documentation: Option<String>,
202    /// Supported authentication methods (JSON array)
203    pub authentication: Option<String>,
204    /// Environment variables required (JSON array)
205    pub env: Option<String>,
206    /// npm package name for the provider's SDK
207    pub npm: Option<String>,
208    /// Provider website URL
209    pub url: Option<String>,
210    /// Date the provider was established
211    pub established_date: Option<String>,
212    /// Date the provider details were last updated
213    pub last_updated: Option<String>,
214}
215/// Link check cache entry
216#[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    /// Unique identifier for the cache entry
221    pub id: Option<i64>,
222    /// The URL that was checked
223    pub url: Option<String>,
224    /// HTTP status code returned
225    pub status_code: Option<i32>,
226    /// Whether the URL was reachable
227    pub is_reachable: Option<bool>,
228    /// When the URL was checked
229    pub checked_at: Option<DateTime<Utc>>,
230    /// When the cache entry expires
231    pub expires_at: Option<DateTime<Utc>>,
232}
233/// Research activity cache entry
234#[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    /// Unique identifier for the cache entry
239    pub id: Option<i64>,
240    /// Research activity identifier
241    pub identifier: Option<String>,
242    /// Source bucket name
243    pub source_bucket: Option<String>,
244    /// Research activity title
245    pub title: Option<String>,
246    /// When the activity was downloaded
247    pub downloaded_at: Option<DateTime<Utc>>,
248    /// Local file path where the activity is stored
249    pub file_path: Option<String>,
250}
251/// Validation/check result history
252#[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    /// Unique identifier for the validation entry
257    pub id: Option<i64>,
258    /// Path to the file that was validated
259    pub path: Option<String>,
260    /// Type of check performed (schema, link, prose, readability)
261    pub check_type: Option<String>,
262    /// Whether the validation passed
263    pub success: Option<bool>,
264    /// Validation message or error details
265    pub message: Option<String>,
266    /// When the validation was performed
267    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    /// Returns CREATE TABLE SQL statement
745    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    /// Returns the table name as a string
875    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    /// Print all table rows
938    /// ### Example
939    /// ```ignore
940    /// use acorn::io::database::{TableSchemaProvider, schema::Table};
941    ///
942    /// Table::Activity.print(database_path.clone());
943    /// ```
944    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    /// Get table rows
973    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    /// Check if the cache entry is expired
1346    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}