tiller_sync/commands/
query.rs

1//! Query commands for executing SQL and retrieving schema information.
2//!
3//! This module provides:
4//! - `query`: Execute arbitrary read-only SQL queries
5//! - `schema`: Retrieve database schema information
6
7use crate::args::{QueryArgs, SchemaArgs};
8use crate::commands::Out;
9use crate::error::{ErrorType, IntoResult};
10use crate::Config;
11use crate::Result;
12use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14use std::fmt::{Debug, Display, Formatter};
15
16// =============================================================================
17// Rows type for query results
18// =============================================================================
19
20/// Query result rows in the requested output format.
21#[derive(Clone, Serialize, Deserialize)]
22#[serde(untagged)]
23pub enum Rows {
24    /// JSON array of objects where each row is a self-describing object with column names as keys.
25    Json(serde_json::Value),
26    /// Markdown table as a single formatted string.
27    Table(String),
28    /// CSV data as a properly escaped string.
29    Csv(String),
30}
31
32impl Debug for Rows {
33    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34        match self {
35            Rows::Json(v) => write!(f, "Rows::Json({:?})", v),
36            Rows::Table(s) => write!(f, "Rows::Table({} chars)", s.len()),
37            Rows::Csv(s) => write!(f, "Rows::Csv({} chars)", s.len()),
38        }
39    }
40}
41
42impl Display for Rows {
43    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
44        match self {
45            Rows::Json(v) => {
46                if let Ok(s) = serde_json::to_string_pretty(v) {
47                    write!(f, "{}", s)
48                } else {
49                    write!(f, "{:?}", v)
50                }
51            }
52            Rows::Table(s) => write!(f, "{}", s),
53            Rows::Csv(s) => write!(f, "{}", s),
54        }
55    }
56}
57
58// =============================================================================
59// Schema types for schema command
60// =============================================================================
61
62/// Database schema information.
63#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
64pub struct Schema {
65    /// List of tables in the database.
66    pub tables: Vec<TableInfo>,
67}
68
69/// Information about a database table.
70#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
71pub struct TableInfo {
72    /// Table name.
73    pub name: String,
74    /// Number of rows in the table.
75    pub row_count: u64,
76    /// Columns in the table.
77    pub columns: Vec<ColumnInfo>,
78    /// Indexes on the table.
79    pub indexes: Vec<IndexInfo>,
80    /// Foreign key constraints.
81    pub foreign_keys: Vec<ForeignKeyInfo>,
82}
83
84/// Information about a table column.
85#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
86pub struct ColumnInfo {
87    /// Column name.
88    pub name: String,
89    /// SQLite data type.
90    pub data_type: String,
91    /// Whether the column allows NULL values.
92    pub nullable: bool,
93    /// Whether the column is part of the primary key.
94    pub primary_key: bool,
95    /// Description of the column from model doc comments (if available).
96    pub description: Option<String>,
97}
98
99/// Information about a table index.
100#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
101pub struct IndexInfo {
102    /// Index name.
103    pub name: String,
104    /// Columns included in the index.
105    pub columns: Vec<String>,
106    /// Whether the index enforces uniqueness.
107    pub unique: bool,
108}
109
110/// Information about a foreign key constraint.
111#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
112pub struct ForeignKeyInfo {
113    /// Columns in this table that are part of the foreign key.
114    pub columns: Vec<String>,
115    /// Table that this foreign key references.
116    pub references_table: String,
117    /// Columns in the referenced table.
118    pub references_columns: Vec<String>,
119}
120
121// =============================================================================
122// Command implementations
123// =============================================================================
124
125/// Execute a read-only SQL query against the local SQLite database.
126///
127/// The query interface enforces read-only access using a separate SQLite connection opened with
128/// `?mode=ro`. Any write attempt (INSERT, UPDATE, DELETE) will be rejected by SQLite.
129pub async fn query(config: Config, args: QueryArgs) -> Result<Out<Rows>> {
130    config
131        .db()
132        .execute_query(args)
133        .await
134        .pub_result(ErrorType::Database)
135}
136
137/// Retrieve database schema information.
138///
139/// Returns tables, columns, types, indexes, foreign keys, column descriptions, and row counts.
140pub async fn schema(config: Config, args: SchemaArgs) -> Result<Out<Schema>> {
141    config
142        .db()
143        .get_schema(args)
144        .await
145        .pub_result(ErrorType::Database)
146}