Skip to main content

everruns_core/
session_sqldb.rs

1// Session SQL Database types and trait
2//
3// Types and async trait for session-scoped SQL databases.
4// Defined in core so capability tools can depend on them.
5// Implementations live in the session-sqldb crate.
6
7use async_trait::async_trait;
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10
11use crate::typed_id::SessionId;
12
13/// Metadata about a session database (no content/pages).
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct DatabaseInfo {
16    pub name: String,
17    pub size_bytes: i64,
18    pub page_count: i32,
19    pub created_at: DateTime<Utc>,
20    pub updated_at: DateTime<Utc>,
21}
22
23/// Result of a SELECT query.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct SqlQueryResult {
26    pub columns: Vec<String>,
27    pub rows: Vec<Vec<serde_json::Value>>,
28    pub row_count: usize,
29    /// True if results were truncated due to limits
30    #[serde(skip_serializing_if = "std::ops::Not::not")]
31    pub truncated: bool,
32}
33
34/// Result of an INSERT/UPDATE/DELETE/DDL statement.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct SqlExecuteResult {
37    pub rows_affected: u64,
38}
39
40/// Schema of a table.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct TableSchema {
43    pub name: String,
44    pub columns: Vec<ColumnSchema>,
45    pub row_count: i64,
46}
47
48/// Schema of a column.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ColumnSchema {
51    pub name: String,
52    #[serde(rename = "type")]
53    pub column_type: String,
54    pub notnull: bool,
55    pub pk: bool,
56    pub default_value: Option<String>,
57}
58
59/// Error type for session SQL database operations.
60///
61/// Tool-visible errors (validation, not found, limits) vs internal errors
62/// are distinguished by the caller when converting to ToolExecutionResult.
63#[derive(Debug, thiserror::Error)]
64pub enum SessionSqlDbError {
65    #[error("database not found: {0}")]
66    DatabaseNotFound(String),
67
68    #[error("database already exists: {0}")]
69    DatabaseAlreadyExists(String),
70
71    #[error("invalid database name: {0}")]
72    InvalidDatabaseName(String),
73
74    #[error("database limit exceeded: {0}")]
75    LimitExceeded(String),
76
77    #[error("query error: {0}")]
78    QueryError(String),
79
80    #[error("query timeout after {0} seconds")]
81    QueryTimeout(u64),
82
83    #[error("result too large: {0}")]
84    ResultTooLarge(String),
85
86    #[error("operation blocked by authorizer: {0}")]
87    AuthorizerBlocked(String),
88
89    #[error("internal error: {0}")]
90    Internal(String),
91}
92
93impl SessionSqlDbError {
94    /// Whether this error should be shown to the LLM as a tool error (vs internal error).
95    pub fn is_tool_error(&self) -> bool {
96        matches!(
97            self,
98            Self::DatabaseNotFound(_)
99                | Self::DatabaseAlreadyExists(_)
100                | Self::InvalidDatabaseName(_)
101                | Self::LimitExceeded(_)
102                | Self::QueryError(_)
103                | Self::QueryTimeout(_)
104                | Self::ResultTooLarge(_)
105                | Self::AuthorizerBlocked(_)
106        )
107    }
108}
109
110/// Async trait for session-scoped SQL database operations.
111///
112/// Implementations:
113/// - InMemorySqlDbBackend (DEV_MODE) in session-sqldb crate
114/// - Future: PostgreSQL VFS backend
115#[async_trait]
116pub trait SessionSqlDbStore: Send + Sync {
117    /// Create a named database in the session.
118    async fn create_database(
119        &self,
120        session_id: SessionId,
121        name: &str,
122    ) -> Result<DatabaseInfo, SessionSqlDbError>;
123
124    /// List all databases for a session.
125    async fn list_databases(
126        &self,
127        session_id: SessionId,
128    ) -> Result<Vec<DatabaseInfo>, SessionSqlDbError>;
129
130    /// Get metadata for a specific database.
131    async fn get_database(
132        &self,
133        session_id: SessionId,
134        name: &str,
135    ) -> Result<Option<DatabaseInfo>, SessionSqlDbError>;
136
137    /// Delete a database and all its pages.
138    async fn delete_database(
139        &self,
140        session_id: SessionId,
141        name: &str,
142    ) -> Result<bool, SessionSqlDbError>;
143
144    /// Execute DDL/DML SQL. Auto-creates database if it doesn't exist.
145    async fn sql_execute(
146        &self,
147        session_id: SessionId,
148        db_name: &str,
149        sql: &str,
150    ) -> Result<SqlExecuteResult, SessionSqlDbError>;
151
152    /// Execute read-only SQL query. Returns columns and rows.
153    async fn sql_query(
154        &self,
155        session_id: SessionId,
156        db_name: &str,
157        sql: &str,
158    ) -> Result<SqlQueryResult, SessionSqlDbError>;
159
160    /// Get schema for all tables (or a specific table) in a database.
161    async fn sql_schema(
162        &self,
163        session_id: SessionId,
164        db_name: &str,
165        table: Option<&str>,
166    ) -> Result<Vec<TableSchema>, SessionSqlDbError>;
167}