Skip to content

Commit

Permalink
Introduce ReadOnly and ReadWrite open mode for SQLite
Browse files Browse the repository at this point in the history
  • Loading branch information
marcua committed Oct 19, 2024
1 parent 61377b1 commit 61438e8
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 9 deletions.
11 changes: 9 additions & 2 deletions src/bin/ayb_isolated_runner.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use ayb::hosted_db::sqlite::query_sqlite;
use ayb::hosted_db::QueryMode;
use std::env;
use std::path::PathBuf;

/// This binary runs a query against a database and returns the
/// result in QueryResults format. To run it, you would type:
/// $ ayb_isolated_runner database.sqlite SELECT xyz FROM ...
/// $ ayb_isolated_runner database.sqlite [0=read-only|1=read-write] SELECT xyz FROM ...
///
/// This command is meant to be run inside a sandbox that isolates
/// parallel invocations of the command from accessing each
Expand All @@ -13,8 +14,14 @@ use std::path::PathBuf;
fn main() -> Result<(), serde_json::Error> {
let args: Vec<String> = env::args().collect();
let db_file = &args[1];
let query_mode = QueryMode::try_from(
args[2]
.parse::<i16>()
.expect("query mode should be an integer"),
)
.expect("query mode should be 0 or 1");
let query = (args[2..]).to_vec();
let result = query_sqlite(&PathBuf::from(db_file), &query.join(" "), false);
let result = query_sqlite(&PathBuf::from(db_file), &query.join(" "), false, query_mode);
match result {
Ok(result) => println!("{}", serde_json::to_string(&result)?),
Err(error) => eprintln!("{}", serde_json::to_string(&error)?),
Expand Down
17 changes: 16 additions & 1 deletion src/hosted_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,23 @@ use crate::error::AybError;
use crate::formatting::TabularFormatter;
use crate::hosted_db::sqlite::potentially_isolated_sqlite_query;
use crate::server::config::AybConfigIsolation;
use crate::try_from_i16;
use prettytable::{Cell, Row, Table};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::vec::Vec;

#[repr(i16)]
pub enum QueryMode {
ReadOnly = 0,
ReadWrite = 1,
}

try_from_i16!(QueryMode, {
0 => QueryMode::ReadOnly,
1 => QueryMode::ReadWrite
});

#[derive(Serialize, Debug, Deserialize)]
pub struct QueryResult {
pub fields: Vec<String>,
Expand Down Expand Up @@ -48,9 +60,12 @@ pub async fn run_query(
query: &str,
db_type: &DBType,
isolation: &Option<AybConfigIsolation>,
query_mode: QueryMode,
) -> Result<QueryResult, AybError> {
match db_type {
DBType::Sqlite => Ok(potentially_isolated_sqlite_query(path, query, isolation).await?),
DBType::Sqlite => {
Ok(potentially_isolated_sqlite_query(path, query, isolation, query_mode).await?)
}
_ => Err(AybError::Other {
message: "Unsupported DB type".to_string(),
}),
Expand Down
18 changes: 15 additions & 3 deletions src/hosted_db/sqlite.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::error::AybError;
use crate::hosted_db::{sandbox::run_in_sandbox, QueryResult};
use crate::hosted_db::{sandbox::run_in_sandbox, QueryMode, QueryResult};
use crate::server::config::AybConfigIsolation;
use rusqlite::config::DbConfig;
use rusqlite::limits::Limit;
Expand All @@ -14,8 +14,19 @@ pub fn query_sqlite(
path: &PathBuf,
query: &str,
allow_unsafe: bool,
query_mode: QueryMode,
) -> Result<QueryResult, AybError> {
let conn = rusqlite::Connection::open(path)?;
// The flags below are the default `open` flags in `rusqlite`
// except for `..READ_ONLY` and `..READ_WRITE`.
let mut open_flags = rusqlite::OpenFlags::SQLITE_OPEN_CREATE
| rusqlite::OpenFlags::SQLITE_OPEN_URI
| rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX;
open_flags = open_flags
| match query_mode {
QueryMode::ReadOnly => rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
QueryMode::ReadWrite => rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE,
};
let conn = rusqlite::Connection::open_with_flags(path, open_flags)?;

if !allow_unsafe {
// Disable the usage of ATTACH
Expand Down Expand Up @@ -63,6 +74,7 @@ pub async fn potentially_isolated_sqlite_query(
path: &PathBuf,
query: &str,
isolation: &Option<AybConfigIsolation>,
query_mode: QueryMode,
) -> Result<QueryResult, AybError> {
if let Some(isolation) = isolation {
let result = run_in_sandbox(Path::new(&isolation.nsjail_path), path, query).await?;
Expand All @@ -88,5 +100,5 @@ pub async fn potentially_isolated_sqlite_query(
}

// No isolation configuration, so run the query without a sandbox.
query_sqlite(path, query, false)
query_sqlite(path, query, false, query_mode)
}
12 changes: 10 additions & 2 deletions src/server/endpoints/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::ayb_db::models::{DBType, InstantiatedEntity};

use crate::error::AybError;
use crate::hosted_db::paths::current_database_path;
use crate::hosted_db::{run_query, QueryResult};
use crate::hosted_db::{run_query, QueryMode, QueryResult};
use crate::http::structs::EntityDatabasePath;
use crate::server::config::AybConfig;
use crate::server::permissions::can_query;
Expand All @@ -26,7 +26,15 @@ async fn query(
if can_query(&authenticated_entity, &database) {
let db_type = DBType::try_from(database.db_type)?;
let db_path = current_database_path(entity_slug, database_slug, &ayb_config.data_path)?;
let result = run_query(&db_path, &query, &db_type, &ayb_config.isolation).await?;
// TODO(marcua): Determine read-only or read-write
let result = run_query(
&db_path,
&query,
&db_type,
&ayb_config.isolation,
QueryMode::ReadWrite,
)
.await?;
Ok(web::Json(result))
} else {
Err(AybError::Other {
Expand Down
9 changes: 8 additions & 1 deletion src/server/snapshots/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::hosted_db::paths::{
pathbuf_to_parent,
};
use crate::hosted_db::sqlite::query_sqlite;
use crate::hosted_db::QueryMode;
use crate::server::config::{AybConfig, SqliteSnapshotMethod};
use crate::server::snapshots::hashes::hash_db_directory;
use crate::server::snapshots::models::{Snapshot, SnapshotType};
Expand Down Expand Up @@ -164,13 +165,19 @@ pub async fn snapshot_database(
// Run in unsafe mode to allow backup process to
// attach to destination database.
true,
QueryMode::ReadWrite,
)?;
if !result.rows.is_empty() {
return Err(AybError::SnapshotError {
message: format!("Unexpected snapshot result: {:?}", result),
});
}
let result = query_sqlite(&snapshot_path, "PRAGMA integrity_check;", false)?;
let result = query_sqlite(
&snapshot_path,
"PRAGMA integrity_check;",
false,
QueryMode::ReadWrite,
)?;
if result.fields.len() != 1
|| result.rows.len() != 1
|| result.rows[0][0] != Some("ok".to_string())
Expand Down

0 comments on commit 61438e8

Please sign in to comment.