Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src-tauri/src/drivers/driver_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -737,8 +737,8 @@ pub trait DatabaseDriver: Send + Sync {
/// rejects any write the classifier missed.
///
/// The default implementation delegates to [`Self::execute_query`]:
/// plugin drivers have no read-only mode, so for them classification is
/// the only safeguard.
/// for drivers without a read-only mode, classification is the only
/// safeguard.
async fn execute_read_only_query(
&self,
params: &ConnectionParams,
Expand Down
90 changes: 90 additions & 0 deletions src-tauri/src/plugins/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,30 @@ impl DatabaseDriver for RpcDriver {
serde_json::from_value(res).map_err(|e| e.to_string())
}

async fn execute_read_only_query(
&self,
params: &ConnectionParams,
query: &str,
limit: Option<u32>,
page: u32,
schema: Option<&str>,
) -> Result<QueryResult, String> {
match self
.process
.call_detailed(
"execute_read_only_query",
json!({ "params": self.with_primary_database(params), "query": query, "limit": limit, "page": page, "schema": schema }),
)
.await
{
Ok(res) => serde_json::from_value(res).map_err(|e| e.to_string()),
Err(error) if is_method_not_found(&error) => {
self.execute_query(params, query, limit, page, schema).await
}
Err(error) => Err(error.to_string()),
}
}

async fn execute_batch(
&self,
params: &ConnectionParams,
Expand Down Expand Up @@ -3194,6 +3218,72 @@ mod tests {
assert_eq!(result.unwrap_err(), "connection lost");
}

fn read_only_result() -> Value {
json!({ "columns": ["n"], "rows": [[1]], "affected_rows": 0, "pagination": null })
}

#[tokio::test]
async fn rpc_driver_forwards_execute_read_only_query() {
let driver = test_driver(|request| {
assert_eq!(request.method, "execute_read_only_query");
assert_eq!(request.params["query"], "SELECT 1 AS n");
assert_eq!(request.params["schema"], "public");
read_only_result()
});

let result = driver
.execute_read_only_query(
&test_connection_params(),
"SELECT 1 AS n",
Some(10),
1,
Some("public"),
)
.await
.expect("execute_read_only_query");

assert_eq!(result.rows, vec![vec![json!(1)]]);
}

#[tokio::test]
async fn rpc_driver_execute_read_only_query_falls_back_when_the_plugin_lacks_it() {
let driver = test_driver_detailed(|request| match request.method.as_str() {
"execute_read_only_query" => Err(PluginCallError::Remote(JsonRpcError {
code: -32601,
message: "not implemented".to_string(),
})),
"execute_query" => Ok(read_only_result()),
other => panic!("unexpected method: {other}"),
});

let result = driver
.execute_read_only_query(&test_connection_params(), "SELECT 1 AS n", None, 1, None)
.await
.expect("fallback to execute_query");

assert_eq!(result.rows, vec![vec![json!(1)]]);
}

#[tokio::test]
async fn rpc_driver_execute_read_only_query_propagates_other_errors() {
let driver = test_driver_detailed(|request| match request.method.as_str() {
"execute_read_only_query" => Err(PluginCallError::Remote(JsonRpcError {
code: -32603,
message: "cannot execute INSERT in a read-only transaction".to_string(),
})),
other => panic!("unexpected method: {other}"),
});

let result = driver
.execute_read_only_query(&test_connection_params(), "SELECT f()", None, 1, None)
.await;

assert_eq!(
result.unwrap_err(),
"cannot execute INSERT in a read-only transaction"
);
}

#[tokio::test]
async fn rpc_driver_forwards_get_materialized_view_columns() {
let driver = test_driver(|request| {
Expand Down