mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-18 17:14:20 +01:00
Implement pragma database_list
And also the CLI option .databases, which is just manipulating that. This is one step in the road to attach.
This commit is contained in:
@@ -119,7 +119,7 @@ Turso aims to be fully compatible with SQLite, with opt-in features not supporte
|
||||
| PRAGMA count_changes | Not Needed | deprecated in SQLite |
|
||||
| PRAGMA data_store_directory | Not Needed | deprecated in SQLite |
|
||||
| PRAGMA data_version | No | |
|
||||
| PRAGMA database_list | No | |
|
||||
| PRAGMA database_list | Yes | |
|
||||
| PRAGMA default_cache_size | Not Needed | deprecated in SQLite |
|
||||
| PRAGMA defer_foreign_keys | No | |
|
||||
| PRAGMA empty_result_callbacks | Not Needed | deprecated in SQLite |
|
||||
|
||||
74
cli/app.rs
74
cli/app.rs
@@ -624,6 +624,11 @@ impl Limbo {
|
||||
let _ = self.writeln(e.to_string());
|
||||
}
|
||||
}
|
||||
Command::Databases => {
|
||||
if let Err(e) = self.display_databases() {
|
||||
let _ = self.writeln(e.to_string());
|
||||
}
|
||||
}
|
||||
Command::Opcodes(args) => {
|
||||
if let Some(opcode) = args.opcode {
|
||||
for op in &OPCODE_DESCRIPTIONS {
|
||||
@@ -1100,6 +1105,75 @@ impl Limbo {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_databases(&mut self) -> anyhow::Result<()> {
|
||||
let sql = "PRAGMA database_list";
|
||||
|
||||
match self.conn.query(sql) {
|
||||
Ok(Some(ref mut rows)) => {
|
||||
loop {
|
||||
match rows.step()? {
|
||||
StepResult::Row => {
|
||||
let row = rows.row().unwrap();
|
||||
if let (
|
||||
Ok(Value::Integer(_seq)),
|
||||
Ok(Value::Text(name)),
|
||||
Ok(file_value),
|
||||
) = (
|
||||
row.get::<&Value>(0),
|
||||
row.get::<&Value>(1),
|
||||
row.get::<&Value>(2),
|
||||
) {
|
||||
let file = match file_value {
|
||||
Value::Text(path) => path.as_str(),
|
||||
Value::Null => "",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
// Format like SQLite: "main: /path/to/file r/w"
|
||||
let file_display = if file.is_empty() {
|
||||
"\"\"".to_string()
|
||||
} else {
|
||||
file.to_string()
|
||||
};
|
||||
|
||||
// Detect readonly mode from connection
|
||||
let mode = if self.conn.is_readonly() {
|
||||
"r/o"
|
||||
} else {
|
||||
"r/w"
|
||||
};
|
||||
|
||||
let _ = self.writeln(format!(
|
||||
"{}: {} {}",
|
||||
name.as_str(),
|
||||
file_display,
|
||||
mode
|
||||
));
|
||||
}
|
||||
}
|
||||
StepResult::IO => {
|
||||
rows.run_once()?;
|
||||
}
|
||||
StepResult::Interrupt => break,
|
||||
StepResult::Done => break,
|
||||
StepResult::Busy => {
|
||||
let _ = self.writeln("database is busy");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
let _ = self.writeln("No results returned from the query.");
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(anyhow::anyhow!("Error querying database list: {}", err));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_remaining_input(&mut self) {
|
||||
if self.input_buff.is_empty() {
|
||||
return;
|
||||
|
||||
@@ -62,6 +62,8 @@ pub enum Command {
|
||||
Echo(EchoArgs),
|
||||
/// Display tables
|
||||
Tables(TablesArgs),
|
||||
/// Display attached databases
|
||||
Databases,
|
||||
/// Import data from FILE into TABLE
|
||||
#[command(name = "import", display_name = ".import")]
|
||||
Import(ImportArgs),
|
||||
|
||||
28
cli/input.rs
28
cli/input.rs
@@ -200,39 +200,43 @@ pub const AFTER_HELP_MSG: &str = r#"Usage Examples:
|
||||
4. To list all tables:
|
||||
.tables
|
||||
|
||||
5. To list all available SQL opcodes:
|
||||
5. To list all databases:
|
||||
.databases
|
||||
|
||||
6. To list all available SQL opcodes:
|
||||
.opcodes
|
||||
|
||||
6. To change the current output mode to 'pretty':
|
||||
7. To change the current output mode to 'pretty':
|
||||
.mode pretty
|
||||
|
||||
7. Send output to STDOUT if no file is specified:
|
||||
8. Send output to STDOUT if no file is specified:
|
||||
.output
|
||||
|
||||
8. To change the current working directory to '/tmp':
|
||||
9. To change the current working directory to '/tmp':
|
||||
.cd /tmp
|
||||
|
||||
9. Show the current values of settings:
|
||||
10. Show the current values of settings:
|
||||
.show
|
||||
|
||||
10. To import csv file 'sample.csv' into 'csv_table' table:
|
||||
11. To import csv file 'sample.csv' into 'csv_table' table:
|
||||
.import --csv sample.csv csv_table
|
||||
|
||||
11. To display the database contents as SQL:
|
||||
12. To display the database contents as SQL:
|
||||
.dump
|
||||
|
||||
12. To load an extension library:
|
||||
13. To load an extension library:
|
||||
.load /target/debug/liblimbo_regexp
|
||||
|
||||
13. To list all available VFS:
|
||||
14. To list all available VFS:
|
||||
.listvfs
|
||||
14. To show names of indexes:
|
||||
|
||||
15. To show names of indexes:
|
||||
.indexes ?TABLE?
|
||||
|
||||
15. To turn on column headers in list mode:
|
||||
16. To turn on column headers in list mode:
|
||||
.headers on
|
||||
|
||||
16. To turn off column headers in list mode:
|
||||
17. To turn off column headers in list mode:
|
||||
.headers off
|
||||
|
||||
Note:
|
||||
|
||||
17
core/lib.rs
17
core/lib.rs
@@ -893,6 +893,23 @@ impl Connection {
|
||||
self.page_size.get()
|
||||
}
|
||||
|
||||
pub fn get_database_canonical_path(&self) -> String {
|
||||
if self._db.path == ":memory:" {
|
||||
// For in-memory databases, SQLite shows empty string
|
||||
String::new()
|
||||
} else {
|
||||
// For file databases, try show the full absolute path if that doesn't fail
|
||||
match std::fs::canonicalize(&self._db.path) {
|
||||
Ok(abs_path) => abs_path.to_string_lossy().to_string(),
|
||||
Err(_) => self._db.path.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_readonly(&self) -> bool {
|
||||
self._db.open_flags.contains(OpenFlags::ReadOnly)
|
||||
}
|
||||
|
||||
/// Reset the page size for the current connection.
|
||||
///
|
||||
/// Specifying a new page size does not change the page size immediately.
|
||||
|
||||
@@ -46,6 +46,7 @@ pub fn pragma_for(pragma: &PragmaName) -> Pragma {
|
||||
| PragmaFlags::NoColumns1,
|
||||
&["cache_size"],
|
||||
),
|
||||
DatabaseList => Pragma::new(PragmaFlags::Result0, &["seq", "name", "file"]),
|
||||
JournalMode => Pragma::new(
|
||||
PragmaFlags::NeedSchema | PragmaFlags::Result0 | PragmaFlags::SchemaReq,
|
||||
&["journal_mode"],
|
||||
|
||||
@@ -250,6 +250,7 @@ fn update_pragma(
|
||||
connection.set_capture_data_changes(opts);
|
||||
Ok((program, TransactionMode::Write))
|
||||
}
|
||||
PragmaName::DatabaseList => unreachable!("database_list cannot be set"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,6 +280,27 @@ fn query_pragma(
|
||||
program.add_pragma_result_column(pragma.to_string());
|
||||
Ok((program, TransactionMode::None))
|
||||
}
|
||||
PragmaName::DatabaseList => {
|
||||
let base_reg = register;
|
||||
program.alloc_registers(2);
|
||||
|
||||
// For now, we only show the main database (seq=0)
|
||||
// seq (sequence number)
|
||||
program.emit_int(0, base_reg);
|
||||
|
||||
// name
|
||||
program.emit_string8("main".into(), base_reg + 1);
|
||||
|
||||
let file_path = connection.get_database_canonical_path();
|
||||
program.emit_string8(file_path, base_reg + 2);
|
||||
|
||||
program.emit_result_row(base_reg, 3);
|
||||
let pragma = pragma_for(&pragma);
|
||||
for col_name in pragma.columns.iter() {
|
||||
program.add_pragma_result_column(col_name.to_string());
|
||||
}
|
||||
Ok((program, TransactionMode::None))
|
||||
}
|
||||
PragmaName::JournalMode => {
|
||||
program.emit_string8("wal".into(), register);
|
||||
program.emit_result_row(register, 1);
|
||||
|
||||
@@ -1749,6 +1749,8 @@ pub enum PragmaName {
|
||||
AutoVacuum,
|
||||
/// `cache_size` pragma
|
||||
CacheSize,
|
||||
/// List databases
|
||||
DatabaseList,
|
||||
/// Run integrity check on the database file
|
||||
IntegrityCheck,
|
||||
/// `journal_mode` pragma
|
||||
|
||||
Reference in New Issue
Block a user