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}