Merge 'Add query only pragma' from bit-aloo

This PR adds support for the PRAGMA `query_only` pragma, which enables
or disables write operations on a database connection. It allows
applications to mark the connection as read-only at runtime.

Reviewed-by: Preston Thorpe <preston@turso.tech>

Closes #2498
This commit is contained in:
Pekka Enberg
2025-08-08 08:33:29 +03:00
committed by GitHub
6 changed files with 57 additions and 2 deletions

View File

@@ -150,7 +150,7 @@ Turso aims to be fully compatible with SQLite, with opt-in features not supporte
| PRAGMA page_size | Yes | |
| PRAGMA parser_trace | No | |
| PRAGMA pragma_list | Yes | |
| PRAGMA query_only | No | |
| PRAGMA query_only | Yes | |
| PRAGMA quick_check | No | |
| PRAGMA read_uncommitted | No | |
| PRAGMA recursive_triggers | No | |

View File

@@ -371,6 +371,7 @@ impl Database {
capture_data_changes: RefCell::new(CaptureDataChangesMode::Off),
closed: Cell::new(false),
attached_databases: RefCell::new(DatabaseCatalog::new()),
query_only: Cell::new(false),
});
let builtin_syms = self.builtin_syms.borrow();
// add built-in extensions symbols to the connection to prevent having to load each time
@@ -726,6 +727,7 @@ pub struct Connection {
closed: Cell<bool>,
/// Attached databases
attached_databases: RefCell<DatabaseCatalog>,
query_only: Cell<bool>,
}
impl Connection {
@@ -1738,6 +1740,14 @@ impl Connection {
self.pager.borrow().clone()
}
pub fn get_query_only(&self) -> bool {
self.query_only.get()
}
pub fn set_query_only(&self, value: bool) {
self.query_only.set(value);
}
#[cfg(feature = "fs")]
/// Copy the current Database and write out to a new file.
/// TODO: sqlite3 instead essentially does the equivalent of

View File

@@ -98,6 +98,10 @@ pub fn pragma_for(pragma: &PragmaName) -> Pragma {
PragmaFlags::NeedSchema | PragmaFlags::Result0 | PragmaFlags::SchemaReq,
&["mode", "table"],
),
QueryOnly => Pragma::new(
PragmaFlags::Result0 | PragmaFlags::NoColumns1,
&["query_only"],
),
}
}

View File

@@ -132,6 +132,10 @@ pub fn translate_inner(
| ast::Stmt::Insert(..)
);
if is_write && connection.get_query_only() {
bail_parse_error!("Cannot execute write statement in query_only mode")
}
let is_select = matches!(stmt, ast::Stmt::Select { .. });
let mut program = match stmt {

View File

@@ -4,7 +4,7 @@
use chrono::Datelike;
use std::rc::Rc;
use std::sync::Arc;
use turso_sqlite3_parser::ast::{self, ColumnDefinition, Expr};
use turso_sqlite3_parser::ast::{self, ColumnDefinition, Expr, Literal, Name};
use turso_sqlite3_parser::ast::{PragmaName, QualifiedName};
use crate::pragma::pragma_for;
@@ -292,6 +292,14 @@ fn update_pragma(
Ok((program, TransactionMode::Write))
}
PragmaName::DatabaseList => unreachable!("database_list cannot be set"),
PragmaName::QueryOnly => query_pragma(
PragmaName::QueryOnly,
schema,
Some(value),
pager,
connection,
program,
),
}
}
@@ -527,6 +535,33 @@ fn query_pragma(
program.add_pragma_result_column(pragma.columns[1].to_string());
Ok((program, TransactionMode::Read))
}
PragmaName::QueryOnly => {
if let Some(value_expr) = value {
let is_query_only = match value_expr {
ast::Expr::Literal(Literal::Numeric(i)) => i.parse::<i64>().unwrap() != 0,
ast::Expr::Literal(Literal::String(ref s))
| ast::Expr::Name(Name::Ident(ref s)) => {
let s = s.to_lowercase();
s == "1" || s == "on" || s == "true"
}
_ => {
return Err(LimboError::ParseError(format!(
"Invalid value for PRAGMA query_only: {value_expr:?}"
)));
}
};
connection.set_query_only(is_query_only);
return Ok((program, TransactionMode::None));
};
let register = program.alloc_register();
let is_query_only = connection.get_query_only();
program.emit_int(is_query_only as i64, register);
program.emit_result_row(register, 1);
program.add_pragma_result_column(pragma.to_string());
Ok((program, TransactionMode::None))
}
}
}

View File

@@ -1816,6 +1816,8 @@ pub enum PragmaName {
PageCount,
/// Return the page size of the database in bytes.
PageSize,
/// make connection query only
QueryOnly,
/// Returns schema version of the database file.
SchemaVersion,
/// returns information about the columns of a table